#include <sys/param.h>
#include <sys/queue.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/sysctl.h>
#include <sys/vmmeter.h>
#include <vm/vm.h>
#include <vm/vm_object.h>
#include <vm/vm_page.h>
#include <vm/vm_map.h>
#include <vm/vm_kern.h>
#include <vm/vm_extern.h>
#include <vm/vm_zone.h>
#include <sys/spinlock2.h>
#include <vm/vm_page2.h>
static MALLOC_DEFINE(M_ZONE, "ZONE", "Zone header");
#define ZONE_ERROR_INVALID 0
#define ZONE_ERROR_NOTFREE 1
#define ZONE_ERROR_ALREADYFREE 2
#define ZONE_ROUNDING 32
#define ZENTRY_FREE 0x12342378
static void *zget(vm_zone_t z, int *tryagainp);
void *
zalloc(vm_zone_t z)
{
globaldata_t gd = mycpu;
vm_zpcpu_t *zpcpu;
void *item;
void *scan;
int tryagain;
long zmax;
long n;
#ifdef INVARIANTS
if (z == NULL)
zerror(ZONE_ERROR_INVALID);
#endif
zpcpu = &z->zpcpu[gd->gd_cpuid];
retry:
item = NULL;
if (zpcpu->zfreecnt > 0) {
crit_enter_gd(gd);
if (zpcpu->zfreecnt > 0) {
item = zpcpu->zitems;
#ifdef INVARIANTS
KASSERT(item != NULL,
("zitems_pcpu unexpectedly NULL"));
if (((void **)item)[1] != (void *)ZENTRY_FREE)
zerror(ZONE_ERROR_NOTFREE);
((void **)item)[1] = NULL;
#endif
zpcpu->zitems = ((void **) item)[0];
--zpcpu->zfreecnt;
++zpcpu->znalloc;
}
crit_exit_gd(gd);
}
zmax = z->zmax_pcpu;
if (zmax < 1024)
zmax = 1024;
n = zmax / 10 + 1;
if (item) {
if (zpcpu->zfreecnt >= n * 4)
return item;
if (spin_trylock(&z->zspin) == 0)
return item;
} else {
spin_lock(&z->zspin);
}
if (z->zfreecnt > z->zfreemin) {
do {
scan = z->zitems;
#ifdef INVARIANTS
KASSERT(scan != NULL, ("zitems unexpectedly NULL"));
if (((void **)scan)[1] != (void *)ZENTRY_FREE)
zerror(ZONE_ERROR_NOTFREE);
#endif
z->zitems = ((void **)scan)[0];
--z->zfreecnt;
((void **)scan)[0] = zpcpu->zitems;
zpcpu->zitems = scan;
++zpcpu->zfreecnt;
} while (--n > 0 && z->zfreecnt > z->zfreemin);
spin_unlock(&z->zspin);
if (item == NULL)
goto retry;
return item;
}
spin_unlock(&z->zspin);
if (item)
return item;
tryagain = 0;
item = zget(z, &tryagain);
if (tryagain)
goto retry;
if (item == NULL && (z->zflags & ZONE_PANICFAIL))
panic("zalloc(%s) failed", z->zname);
return item;
}
void
zfree(vm_zone_t z, void *item)
{
globaldata_t gd = mycpu;
vm_zpcpu_t *zpcpu;
void *tail_item;
long count;
long zmax;
long zmove;
zpcpu = &z->zpcpu[gd->gd_cpuid];
zmax = z->zmax_pcpu;
if (zmax < 1024)
zmax = 1024;
crit_enter_gd(gd);
((void **)item)[0] = zpcpu->zitems;
#ifdef INVARIANTS
if (((void **)item)[1] == (void *)ZENTRY_FREE)
zerror(ZONE_ERROR_ALREADYFREE);
((void **)item)[1] = (void *)ZENTRY_FREE;
#endif
zpcpu->zitems = item;
++zpcpu->zfreecnt;
if (zpcpu->zfreecnt < zmax) {
crit_exit_gd(gd);
return;
}
zmove = zmax / 10 + 1;
if (zpcpu->zfreecnt < zmax + zmove * 4) {
if (spin_trylock(&z->zspin) == 0) {
crit_exit_gd(gd);
return;
}
} else {
zmove += zmax - zpcpu->zfreecnt;
if (zmove <= 2)
zmove = 2;
spin_lock(&z->zspin);
}
tail_item = item;
count = 1;
while (count < zmove && ((void **)tail_item)[0]) {
tail_item = ((void **)tail_item)[0];
++count;
}
zpcpu->zitems = ((void **)tail_item)[0];
zpcpu->zfreecnt -= count;
((void **)tail_item)[0] = z->zitems;
z->zitems = item;
z->zfreecnt += count;
spin_unlock(&z->zspin);
crit_exit_gd(gd);
}
LIST_HEAD(zlist, vm_zone) zlist = LIST_HEAD_INITIALIZER(zlist);
static int sysctl_vm_zone(SYSCTL_HANDLER_ARGS);
static vm_pindex_t zone_kmem_pages, zone_kern_pages;
static long zone_kmem_kvaspace;
int
zinitna(vm_zone_t z, char *name, size_t size, long nentries, uint32_t flags)
{
size_t totsize;
if (z->zflags & ZONE_DESTROYABLE)
panic("zinitna: can't create destroyable zone");
if ((z->zflags & ZONE_BOOT) == 0) {
z->zsize = roundup2(size, ZONE_ROUNDING);
spin_init(&z->zspin, "zinitna");
lockinit(&z->zgetlk, "zgetlk", 0, LK_CANRECURSE);
z->zfreecnt = 0;
z->ztotal = 0;
z->zmax = 0;
z->zname = name;
z->zitems = NULL;
lwkt_gettoken(&vm_token);
LIST_INSERT_HEAD(&zlist, z, zlink);
lwkt_reltoken(&vm_token);
bzero(z->zpcpu, sizeof(z->zpcpu));
}
z->zkmvec = NULL;
z->zkmcur = z->zkmmax = 0;
z->zflags |= flags;
if (z->zflags & ZONE_INTERRUPT) {
totsize = round_page((size_t)z->zsize * nentries);
atomic_add_long(&zone_kmem_kvaspace, totsize);
z->zkva = kmem_alloc_pageable(kernel_map, totsize,
VM_SUBSYS_ZALLOC);
if (z->zkva == 0) {
LIST_REMOVE(z, zlink);
return 0;
}
z->zpagemax = totsize / PAGE_SIZE;
z->zallocflag = VM_ALLOC_SYSTEM | VM_ALLOC_INTERRUPT |
VM_ALLOC_NORMAL | VM_ALLOC_RETRY;
z->zmax += nentries;
z->zmax_pcpu = z->zmax / ncpus / 32;
if (z->zmax_pcpu < 1024)
z->zmax_pcpu = 1024;
if (z->zmax_pcpu * z->zsize > 16*1024*1024)
z->zmax_pcpu = 16*1024*1024 / z->zsize;
} else {
z->zallocflag = VM_ALLOC_NORMAL | VM_ALLOC_SYSTEM;
z->zmax = 0;
z->zmax_pcpu = 8192;
}
if (z->zsize > PAGE_SIZE)
z->zfreemin = 1;
else
z->zfreemin = PAGE_SIZE / z->zsize;
z->zpagecount = 0;
z->zalloc = ZONE_MAXPGLOAD;
if (z->zflags & ZONE_INTERRUPT) {
void *buf;
buf = zget(z, NULL);
if (buf)
zfree(z, buf);
}
return 1;
}
vm_zone_t
zinit(char *name, size_t size, long nentries, uint32_t flags)
{
vm_zone_t z;
z = (vm_zone_t) kmalloc(sizeof (struct vm_zone), M_ZONE, M_NOWAIT);
if (z == NULL)
return NULL;
z->zflags = 0;
if (zinitna(z, name, size, nentries, flags & ~ZONE_DESTROYABLE) == 0) {
kfree(z, M_ZONE);
return NULL;
}
if (flags & ZONE_DESTROYABLE)
z->zflags |= ZONE_DESTROYABLE;
return z;
}
void
zbootinit(vm_zone_t z, char *name, size_t size, void *item, long nitems)
{
long i;
spin_init(&z->zspin, "zbootinit");
lockinit(&z->zgetlk, "zgetlk", 0, LK_CANRECURSE);
bzero(z->zpcpu, sizeof(z->zpcpu));
z->zname = name;
z->zsize = size;
z->zpagemax = 0;
z->zflags = ZONE_BOOT;
z->zfreemin = 0;
z->zallocflag = 0;
z->zpagecount = 0;
z->zalloc = 0;
bzero(item, (size_t)nitems * z->zsize);
z->zitems = NULL;
for (i = 0; i < nitems; i++) {
((void **)item)[0] = z->zitems;
#ifdef INVARIANTS
((void **)item)[1] = (void *)ZENTRY_FREE;
#endif
z->zitems = item;
item = (uint8_t *)item + z->zsize;
}
z->zfreecnt = nitems;
z->zmax = nitems;
z->ztotal = nitems;
lwkt_gettoken(&vm_token);
LIST_INSERT_HEAD(&zlist, z, zlink);
lwkt_reltoken(&vm_token);
}
void
zdestroy(vm_zone_t z)
{
vm_pindex_t i;
if (z == NULL)
panic("zdestroy: null zone");
if ((z->zflags & ZONE_DESTROYABLE) == 0)
panic("zdestroy: undestroyable zone");
lwkt_gettoken(&vm_token);
LIST_REMOVE(z, zlink);
lwkt_reltoken(&vm_token);
KKASSERT((z->zflags & ZONE_INTERRUPT) == 0);
for (i = 0; i < z->zkmcur; i++) {
kmem_free(kernel_map, z->zkmvec[i],
(size_t)z->zalloc * PAGE_SIZE);
atomic_subtract_long(&zone_kern_pages, z->zalloc);
}
if (z->zkmvec != NULL)
kfree(z->zkmvec, M_ZONE);
spin_uninit(&z->zspin);
kfree(z, M_ZONE);
}
static void *
zget(vm_zone_t z, int *tryagainp)
{
vm_page_t pgs[ZONE_MAXPGLOAD];
vm_page_t m;
long nitems;
long savezpc;
size_t nbytes;
size_t noffset;
void *item;
vm_pindex_t npages;
vm_pindex_t nalloc;
vm_pindex_t i;
if (z == NULL)
panic("zget: null zone");
if ((z->zflags & ZONE_INTERRUPT) == 0) {
if (lockmgr(&z->zgetlk, LK_EXCLUSIVE | LK_SLEEPFAIL)) {
*tryagainp = 1;
return NULL;
}
}
if (z->zflags & ZONE_INTERRUPT) {
for (i = 0; i < ZONE_MAXPGLOAD && i < z->zalloc; ++i) {
if (i < 4) {
m = vm_page_alloc(NULL,
mycpu->gd_rand_incr++,
z->zallocflag);
} else {
m = vm_page_alloc(NULL,
mycpu->gd_rand_incr++,
VM_ALLOC_NORMAL |
VM_ALLOC_SYSTEM);
}
if (m == NULL)
break;
pgs[i] = m;
}
nalloc = i;
spin_lock(&z->zspin);
noffset = (size_t)z->zpagecount * PAGE_SIZE;
savezpc = z->zpagecount;
if (z->zpagecount + nalloc > z->zpagemax)
z->zpagecount = z->zpagemax;
else
z->zpagecount += nalloc;
item = (char *)z->zkva + noffset;
npages = z->zpagecount - savezpc;
nitems = ((size_t)(savezpc + npages) * PAGE_SIZE - noffset) /
z->zsize;
atomic_add_long(&zone_kmem_pages, npages);
spin_unlock(&z->zspin);
for (i = 0; i < npages; ++i) {
vm_offset_t zkva;
m = pgs[i];
KKASSERT(m->queue == PQ_NONE);
m->valid = VM_PAGE_BITS_ALL;
vm_page_wire(m);
vm_page_wakeup(m);
zkva = z->zkva + (size_t)(savezpc + i) * PAGE_SIZE;
pmap_kenter(zkva, VM_PAGE_TO_PHYS(m));
bzero((void *)zkva, PAGE_SIZE);
}
for (i = npages; i < nalloc; ++i) {
m = pgs[i];
vm_page_free(m);
}
} else if (z->zflags & ZONE_SPECIAL) {
nbytes = (size_t)z->zalloc * PAGE_SIZE;
z->zpagecount += z->zalloc;
item = (void *)kmem_alloc3(kernel_map, nbytes,
VM_SUBSYS_ZALLOC, KM_KRESERVE);
if (item != NULL) {
atomic_add_long(&zone_kern_pages, z->zalloc);
bzero(item, nbytes);
} else {
nbytes = 0;
}
nitems = nbytes / z->zsize;
} else {
nbytes = (size_t)z->zalloc * PAGE_SIZE;
z->zpagecount += z->zalloc;
item = (void *)kmem_alloc3(kernel_map, nbytes,
VM_SUBSYS_ZALLOC, 0);
if (item != NULL) {
atomic_add_long(&zone_kern_pages, z->zalloc);
bzero(item, nbytes);
if (z->zflags & ZONE_DESTROYABLE) {
if (z->zkmcur == z->zkmmax) {
z->zkmmax =
z->zkmmax==0 ? 1 : z->zkmmax*2;
z->zkmvec = krealloc(z->zkmvec,
z->zkmmax * sizeof(z->zkmvec[0]),
M_ZONE, M_WAITOK);
}
z->zkmvec[z->zkmcur++] = (vm_offset_t)item;
}
} else {
nbytes = 0;
}
nitems = nbytes / z->zsize;
}
spin_lock(&z->zspin);
z->ztotal += nitems;
if (nitems > 1 && (z->zflags & ZONE_SPECIAL)) {
struct globaldata *gd = mycpu;
vm_map_entry_t entry;
crit_enter();
while (gd->gd_vme_avail < 2 && nitems > 1) {
entry = item;
MAPENT_FREELIST(entry) = gd->gd_vme_base;
gd->gd_vme_base = entry;
atomic_add_int(&gd->gd_vme_avail, 1);
item = (uint8_t *)item + z->zsize;
--nitems;
}
crit_exit();
}
if (nitems != 0) {
nitems -= 1;
for (i = 0; i < nitems; i++) {
((void **)item)[0] = z->zitems;
#ifdef INVARIANTS
((void **)item)[1] = (void *)ZENTRY_FREE;
#endif
z->zitems = item;
item = (uint8_t *)item + z->zsize;
}
z->zfreecnt += nitems;
++z->znalloc;
} else if (z->zfreecnt > 0) {
item = z->zitems;
z->zitems = ((void **)item)[0];
#ifdef INVARIANTS
if (((void **)item)[1] != (void *)ZENTRY_FREE)
zerror(ZONE_ERROR_NOTFREE);
((void **) item)[1] = NULL;
#endif
--z->zfreecnt;
++z->znalloc;
} else {
item = NULL;
}
spin_unlock(&z->zspin);
if ((z->zflags & ZONE_INTERRUPT) == 0)
lockmgr(&z->zgetlk, LK_RELEASE);
return item;
}
static int
sysctl_vm_zone(SYSCTL_HANDLER_ARGS)
{
vm_zone_t curzone;
char tmpbuf[128];
char tmpname[14];
int error = 0;
ksnprintf(tmpbuf, sizeof(tmpbuf),
"\nITEM SIZE LIMIT USED FREE REQUESTS\n");
error = SYSCTL_OUT(req, tmpbuf, strlen(tmpbuf));
if (error)
return (error);
lwkt_gettoken(&vm_token);
LIST_FOREACH(curzone, &zlist, zlink) {
size_t i;
size_t len;
int offset;
long freecnt;
long znalloc;
int n;
len = strlen(curzone->zname);
if (len >= (sizeof(tmpname) - 1))
len = (sizeof(tmpname) - 1);
for(i = 0; i < sizeof(tmpname) - 1; i++)
tmpname[i] = ' ';
tmpname[i] = 0;
memcpy(tmpname, curzone->zname, len);
tmpname[len] = ':';
offset = 0;
if (curzone == LIST_FIRST(&zlist)) {
offset = 1;
tmpbuf[0] = '\n';
}
freecnt = curzone->zfreecnt;
znalloc = curzone->znalloc;
for (n = 0; n < ncpus; ++n) {
freecnt += curzone->zpcpu[n].zfreecnt;
znalloc += curzone->zpcpu[n].znalloc;
}
ksnprintf(tmpbuf + offset, sizeof(tmpbuf) - offset,
"%s %6.6lu, %8.8lu, %6.6lu, %6.6lu, %8.8lu\n",
tmpname, curzone->zsize, curzone->zmax,
(curzone->ztotal - freecnt),
freecnt, znalloc);
len = strlen((char *)tmpbuf);
if (LIST_NEXT(curzone, zlink) == NULL)
tmpbuf[len - 1] = 0;
error = SYSCTL_OUT(req, tmpbuf, len);
if (error)
break;
}
lwkt_reltoken(&vm_token);
return (error);
}
#if defined(INVARIANTS)
void
zerror(int error)
{
char *msg;
switch (error) {
case ZONE_ERROR_INVALID:
msg = "zone: invalid zone";
break;
case ZONE_ERROR_NOTFREE:
msg = "zone: entry not free";
break;
case ZONE_ERROR_ALREADYFREE:
msg = "zone: freeing free entry";
break;
default:
msg = "zone: invalid error";
break;
}
panic("%s", msg);
}
#endif
SYSCTL_OID(_vm, OID_AUTO, zone, CTLTYPE_STRING|CTLFLAG_RD, \
NULL, 0, sysctl_vm_zone, "A", "Zone Info");
SYSCTL_LONG(_vm, OID_AUTO, zone_kmem_pages,
CTLFLAG_RD, &zone_kmem_pages, 0, "Number of interrupt safe pages allocated by zone");
SYSCTL_LONG(_vm, OID_AUTO, zone_kmem_kvaspace,
CTLFLAG_RD, &zone_kmem_kvaspace, 0, "KVA space allocated by zone");
SYSCTL_LONG(_vm, OID_AUTO, zone_kern_pages,
CTLFLAG_RD, &zone_kern_pages, 0, "Number of non-interrupt safe pages allocated by zone");