#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: uvm_page.c,v 1.257 2026/05/03 16:02:37 thorpej Exp $");
#include "opt_ddb.h"
#include "opt_uvm.h"
#include "opt_uvmhist.h"
#include "opt_readahead.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/sched.h>
#include <sys/kernel.h>
#include <sys/vnode.h>
#include <sys/proc.h>
#include <sys/radixtree.h>
#include <sys/atomic.h>
#include <sys/cpu.h>
#include <ddb/db_active.h>
#include <uvm/uvm.h>
#include <uvm/uvm_ddb.h>
#include <uvm/uvm_pdpolicy.h>
#include <uvm/uvm_pgflcache.h>
#ifndef UVM_RESERVED_PAGES_PER_CPU
#define UVM_RESERVED_PAGES_PER_CPU 5
#endif
int vm_page_reserve_kernel = UVM_RESERVED_PAGES_PER_CPU;
psize_t physmem;
static vaddr_t virtual_space_start;
static vaddr_t virtual_space_end;
static size_t recolored_pages_memsize ;
static char *recolored_pages_mem;
union uvm_freelist_lock uvm_freelist_locks[PGFL_MAX_BUCKETS]
__cacheline_aligned;
static struct uvm_page_numa_region {
struct uvm_page_numa_region *next;
paddr_t start;
paddr_t size;
u_int numa_id;
} *uvm_page_numa_region;
#ifdef DEBUG
kmutex_t uvm_zerochecklock __cacheline_aligned;
vaddr_t uvm_zerocheckkva;
#endif
void uvm_physseg_init_seg(uvm_physseg_t, struct vm_page *);
void uvm_physseg_seg_chomp_slab(uvm_physseg_t, struct vm_page *, size_t);
struct vm_page *uvm_physseg_seg_alloc_from_slab(uvm_physseg_t, size_t);
static inline void
uvm_pageinsert_object(struct uvm_object *uobj, struct vm_page *pg)
{
KASSERT(uobj == pg->uobject);
KASSERT(rw_write_held(uobj->vmobjlock));
KASSERT((pg->flags & PG_TABLED) == 0);
if ((pg->flags & PG_STAT) != 0) {
const unsigned int status = pg->flags & (PG_CLEAN | PG_DIRTY);
if ((pg->flags & PG_FILE) != 0) {
if (uobj->uo_npages == 0) {
struct vnode *vp = (struct vnode *)uobj;
mutex_enter(vp->v_interlock);
KASSERT((vp->v_iflag & VI_PAGES) == 0);
vp->v_iflag |= VI_PAGES;
vholdl(vp);
mutex_exit(vp->v_interlock);
}
if (UVM_OBJ_IS_VTEXT(uobj)) {
cpu_count(CPU_COUNT_EXECPAGES, 1);
}
cpu_count(CPU_COUNT_FILEUNKNOWN + status, 1);
} else {
cpu_count(CPU_COUNT_ANONUNKNOWN + status, 1);
}
}
pg->flags |= PG_TABLED;
uobj->uo_npages++;
}
static inline int
uvm_pageinsert_tree(struct uvm_object *uobj, struct vm_page *pg)
{
const uint64_t idx = pg->offset >> PAGE_SHIFT;
int error;
KASSERT(rw_write_held(uobj->vmobjlock));
error = radix_tree_insert_node(&uobj->uo_pages, idx, pg);
if (error != 0) {
return error;
}
if ((pg->flags & PG_CLEAN) == 0) {
uvm_obj_page_set_dirty(pg);
}
KASSERT(((pg->flags & PG_CLEAN) == 0) ==
uvm_obj_page_dirty_p(pg));
return 0;
}
static inline void
uvm_pageremove_object(struct uvm_object *uobj, struct vm_page *pg)
{
KASSERT(uobj == pg->uobject);
KASSERT(rw_write_held(uobj->vmobjlock));
KASSERT(pg->flags & PG_TABLED);
if ((pg->flags & PG_STAT) != 0) {
const unsigned int status = pg->flags & (PG_CLEAN | PG_DIRTY);
if ((pg->flags & PG_FILE) != 0) {
if (uobj->uo_npages == 1) {
struct vnode *vp = (struct vnode *)uobj;
mutex_enter(vp->v_interlock);
KASSERT((vp->v_iflag & VI_PAGES) != 0);
vp->v_iflag &= ~VI_PAGES;
holdrelel(vp);
mutex_exit(vp->v_interlock);
}
if (UVM_OBJ_IS_VTEXT(uobj)) {
cpu_count(CPU_COUNT_EXECPAGES, -1);
}
cpu_count(CPU_COUNT_FILEUNKNOWN + status, -1);
} else {
cpu_count(CPU_COUNT_ANONUNKNOWN + status, -1);
}
}
uobj->uo_npages--;
pg->flags &= ~PG_TABLED;
pg->uobject = NULL;
}
static inline void
uvm_pageremove_tree(struct uvm_object *uobj, struct vm_page *pg)
{
struct vm_page *opg __unused;
KASSERT(rw_write_held(uobj->vmobjlock));
opg = radix_tree_remove_node(&uobj->uo_pages, pg->offset >> PAGE_SHIFT);
KASSERT(pg == opg);
}
static void
uvm_page_init_bucket(struct pgfreelist *pgfl, struct pgflbucket *pgb, int num)
{
int i;
pgb->pgb_nfree = 0;
for (i = 0; i < uvmexp.ncolors; i++) {
LIST_INIT(&pgb->pgb_colors[i]);
}
pgfl->pgfl_buckets[num] = pgb;
}
void
uvm_page_init(vaddr_t *kvm_startp, vaddr_t *kvm_endp)
{
static struct uvm_cpu uvm_boot_cpu __cacheline_aligned;
psize_t freepages, pagecount, bucketsize, n;
struct pgflbucket *pgb;
struct vm_page *pagearray;
char *bucketarray;
uvm_physseg_t bank;
int fl, b;
KASSERT(ncpu <= 1);
curcpu()->ci_data.cpu_uvm = &uvm_boot_cpu;
uvmpdpol_init();
for (b = 0; b < __arraycount(uvm_freelist_locks); b++) {
mutex_init(&uvm_freelist_locks[b].lock, MUTEX_DEFAULT, IPL_VM);
}
if (uvm_physseg_get_last() == UVM_PHYSSEG_TYPE_INVALID)
panic("uvm_page_bootstrap: no memory pre-allocated");
freepages = 0;
for (bank = uvm_physseg_get_first();
uvm_physseg_valid_p(bank) ;
bank = uvm_physseg_get_next(bank)) {
freepages += (uvm_physseg_get_end(bank) - uvm_physseg_get_start(bank));
}
if (uvmexp.ncolors == 0)
uvmexp.ncolors = 1;
uvmexp.colormask = uvmexp.ncolors - 1;
KASSERT((uvmexp.colormask & uvmexp.ncolors) == 0);
uvm.bucketcount = 1;
pagecount = ((freepages + 1) << PAGE_SHIFT) /
(PAGE_SIZE + sizeof(struct vm_page));
bucketsize = offsetof(struct pgflbucket, pgb_colors[uvmexp.ncolors]);
bucketsize = roundup2(bucketsize, coherency_unit);
bucketarray = (void *)uvm_pageboot_alloc(
bucketsize * VM_NFREELIST +
pagecount * sizeof(struct vm_page));
pagearray = (struct vm_page *)
(bucketarray + bucketsize * VM_NFREELIST);
for (fl = 0; fl < VM_NFREELIST; fl++) {
pgb = (struct pgflbucket *)(bucketarray + bucketsize * fl);
uvm_page_init_bucket(&uvm.page_free[fl], pgb, 0);
}
memset(pagearray, 0, pagecount * sizeof(struct vm_page));
uvm_pgflcache_init();
for (bank = uvm_physseg_get_first(),
uvm_physseg_seg_chomp_slab(bank, pagearray, pagecount);
uvm_physseg_valid_p(bank);
bank = uvm_physseg_get_next(bank)) {
n = uvm_physseg_get_end(bank) - uvm_physseg_get_start(bank);
uvm_physseg_seg_alloc_from_slab(bank, n);
uvm_physseg_init_seg(bank, pagearray);
pagearray += n;
pagecount -= n;
}
*kvm_startp = round_page(virtual_space_start);
*kvm_endp = trunc_page(virtual_space_end);
uvmexp.reserve_pagedaemon = 1;
uvmexp.reserve_kernel = vm_page_reserve_kernel;
uvm.page_init_done = true;
}
void
uvm_pgfl_lock(void)
{
int i;
for (i = 0; i < __arraycount(uvm_freelist_locks); i++) {
mutex_spin_enter(&uvm_freelist_locks[i].lock);
}
}
void
uvm_pgfl_unlock(void)
{
int i;
for (i = 0; i < __arraycount(uvm_freelist_locks); i++) {
mutex_spin_exit(&uvm_freelist_locks[i].lock);
}
}
void
uvm_setpagesize(void)
{
if (uvmexp.pagesize == 0) {
if (PAGE_SIZE == 0)
panic("uvm_setpagesize: uvmexp.pagesize not set");
uvmexp.pagesize = PAGE_SIZE;
}
uvmexp.pagemask = uvmexp.pagesize - 1;
if ((uvmexp.pagemask & uvmexp.pagesize) != 0)
panic("uvm_setpagesize: page size %u (%#x) not a power of two",
uvmexp.pagesize, uvmexp.pagesize);
for (uvmexp.pageshift = 0; ; uvmexp.pageshift++)
if ((1 << uvmexp.pageshift) == uvmexp.pagesize)
break;
}
vaddr_t
uvm_pageboot_alloc(vsize_t size)
{
static bool initialized = false;
vaddr_t addr;
#if !defined(PMAP_STEAL_MEMORY)
vaddr_t vaddr;
paddr_t paddr;
#endif
if (initialized == false) {
pmap_virtual_space(&virtual_space_start, &virtual_space_end);
virtual_space_start = round_page(virtual_space_start);
virtual_space_end = trunc_page(virtual_space_end);
initialized = true;
}
size = round_page(size);
uvmexp.bootpages += atop(size);
#if defined(PMAP_STEAL_MEMORY)
addr = pmap_steal_memory(size, &virtual_space_start,
&virtual_space_end);
return addr;
#else
if (virtual_space_start == virtual_space_end ||
(virtual_space_end - virtual_space_start) < size)
panic("uvm_pageboot_alloc: out of virtual space");
addr = virtual_space_start;
#ifdef PMAP_GROWKERNEL
if (uvm_maxkaddr < (addr + size)) {
uvm_maxkaddr = pmap_growkernel(addr + size);
if (uvm_maxkaddr < (addr + size))
panic("uvm_pageboot_alloc: pmap_growkernel() failed");
}
#endif
virtual_space_start += size;
for (vaddr = round_page(addr) ; vaddr < addr + size ;
vaddr += PAGE_SIZE) {
if (!uvm_page_physget(&paddr))
panic("uvm_pageboot_alloc: out of memory");
pmap_kenter_pa(vaddr, paddr, VM_PROT_READ|VM_PROT_WRITE, 0);
}
pmap_update(pmap_kernel());
return addr;
#endif
}
#if !defined(PMAP_STEAL_MEMORY)
static bool uvm_page_physget_freelist(paddr_t *, int);
static bool
uvm_page_physget_freelist(paddr_t *paddrp, int freelist)
{
uvm_physseg_t lcv;
#if (VM_PHYSSEG_STRAT == VM_PSTRAT_BIGFIRST)
for (lcv = uvm_physseg_get_last(); uvm_physseg_valid_p(lcv); lcv = uvm_physseg_get_prev(lcv))
#else
for (lcv = uvm_physseg_get_first(); uvm_physseg_valid_p(lcv); lcv = uvm_physseg_get_next(lcv))
#endif
{
if (uvm.page_init_done == true)
panic("uvm_page_physget: called _after_ bootstrap");
if (uvm_page_physunload(lcv, freelist, paddrp))
return true;
}
#if (VM_PHYSSEG_STRAT == VM_PSTRAT_BIGFIRST)
for (lcv = uvm_physseg_get_last(); uvm_physseg_valid_p(lcv); lcv = uvm_physseg_get_prev(lcv))
#else
for (lcv = uvm_physseg_get_first(); uvm_physseg_valid_p(lcv); lcv = uvm_physseg_get_next(lcv))
#endif
{
if (uvm_page_physunload_force(lcv, freelist, paddrp))
return true;
}
return false;
}
bool
uvm_page_physget(paddr_t *paddrp)
{
int i;
for (i = 0; i < VM_NFREELIST; i++)
if (uvm_page_physget_freelist(paddrp, i) == true)
return (true);
return (false);
}
#endif
paddr_t
uvm_vm_page_to_phys(const struct vm_page *pg)
{
return pg->phys_addr & ~(PAGE_SIZE - 1);
}
void
uvm_page_numa_load(paddr_t start, paddr_t size, u_int numa_id)
{
struct uvm_page_numa_region *d;
KASSERT(numa_id < PGFL_MAX_BUCKETS);
d = kmem_alloc(sizeof(*d), KM_SLEEP);
d->start = start;
d->size = size;
d->numa_id = numa_id;
d->next = uvm_page_numa_region;
uvm_page_numa_region = d;
}
static u_int
uvm_page_numa_lookup(struct vm_page *pg)
{
struct uvm_page_numa_region *d;
static bool warned;
paddr_t pa;
KASSERT(uvm_page_numa_region != NULL);
pa = VM_PAGE_TO_PHYS(pg);
for (d = uvm_page_numa_region; d != NULL; d = d->next) {
if (pa >= d->start && pa < d->start + d->size) {
return d->numa_id;
}
}
if (!warned) {
printf("uvm_page_numa_lookup: failed, first pg=%p pa=%#"
PRIxPADDR "\n", pg, VM_PAGE_TO_PHYS(pg));
warned = true;
}
return 0;
}
static void
uvm_page_redim(int newncolors, int newnbuckets)
{
struct pgfreelist npgfl;
struct pgflbucket *opgb, *npgb;
struct pgflist *ohead, *nhead;
struct vm_page *pg;
size_t bucketsize, bucketmemsize, oldbucketmemsize;
int fl, ob, oc, nb, nc, obuckets, ocolors;
char *bucketarray, *oldbucketmem, *bucketmem;
KASSERT(((newncolors - 1) & newncolors) == 0);
if (newncolors <= uvmexp.ncolors &&
newnbuckets == uvm.bucketcount) {
return;
}
if (uvm.page_init_done == false) {
uvmexp.ncolors = newncolors;
return;
}
bucketsize = offsetof(struct pgflbucket, pgb_colors[newncolors]);
bucketsize = roundup2(bucketsize, coherency_unit);
bucketmemsize = bucketsize * newnbuckets * VM_NFREELIST +
coherency_unit - 1;
bucketmem = kmem_zalloc(bucketmemsize, KM_SLEEP);
bucketarray = (char *)roundup2((uintptr_t)bucketmem, coherency_unit);
ocolors = uvmexp.ncolors;
obuckets = uvm.bucketcount;
uvm_pgflcache_pause();
uvm_pgfl_lock();
if (newncolors <= uvmexp.ncolors &&
newnbuckets == uvm.bucketcount) {
uvm_pgfl_unlock();
uvm_pgflcache_resume();
kmem_free(bucketmem, bucketmemsize);
return;
}
uvmexp.ncolors = newncolors;
uvmexp.colormask = uvmexp.ncolors - 1;
uvm.bucketcount = newnbuckets;
for (fl = 0; fl < VM_NFREELIST; fl++) {
memset(&npgfl, 0, sizeof(npgfl));
for (nb = 0; nb < newnbuckets; nb++) {
npgb = (struct pgflbucket *)bucketarray;
uvm_page_init_bucket(&npgfl, npgb, nb);
bucketarray += bucketsize;
}
for (nb = ob = 0; ob < obuckets; ob++) {
opgb = uvm.page_free[fl].pgfl_buckets[ob];
for (oc = 0; oc < ocolors; oc++) {
ohead = &opgb->pgb_colors[oc];
while ((pg = LIST_FIRST(ohead)) != NULL) {
LIST_REMOVE(pg, pageq.list);
KASSERT(
uvm_page_get_bucket(pg) == ob);
KASSERT(fl ==
uvm_page_get_freelist(pg));
if (uvm_page_numa_region != NULL) {
nb = uvm_page_numa_lookup(pg);
} else {
nb = atop(VM_PAGE_TO_PHYS(pg))
/ uvmexp.ncolors / 8
% newnbuckets;
}
uvm_page_set_bucket(pg, nb);
npgb = npgfl.pgfl_buckets[nb];
npgb->pgb_nfree++;
nc = VM_PGCOLOR(pg);
nhead = &npgb->pgb_colors[nc];
LIST_INSERT_HEAD(nhead, pg, pageq.list);
}
}
}
memcpy(&uvm.page_free[fl], &npgfl, sizeof(npgfl));
}
oldbucketmemsize = recolored_pages_memsize;
oldbucketmem = recolored_pages_mem;
recolored_pages_memsize = bucketmemsize;
recolored_pages_mem = bucketmem;
uvm_pgfl_unlock();
uvm_pgflcache_resume();
if (oldbucketmemsize) {
kmem_free(oldbucketmem, oldbucketmemsize);
}
uvm_pager_realloc_emerg();
}
void
uvm_page_recolor(int newncolors)
{
uvm_page_redim(newncolors, uvm.bucketcount);
}
void
uvm_page_rebucket(void)
{
u_int min_numa, max_numa, npackage, shift;
struct cpu_info *ci, *ci2, *ci3;
CPU_INFO_ITERATOR cii;
min_numa = (u_int)-1;
max_numa = 0;
for (CPU_INFO_FOREACH(cii, ci)) {
if (ci->ci_numa_id < min_numa) {
min_numa = ci->ci_numa_id;
}
if (ci->ci_numa_id > max_numa) {
max_numa = ci->ci_numa_id;
}
}
if (min_numa != max_numa && max_numa < PGFL_MAX_BUCKETS) {
aprint_debug("UVM: using NUMA allocation scheme\n");
for (CPU_INFO_FOREACH(cii, ci)) {
ci->ci_data.cpu_uvm->pgflbucket = ci->ci_numa_id;
}
uvm_page_redim(uvmexp.ncolors, max_numa + 1);
return;
}
npackage = curcpu()->ci_nsibling[CPUREL_PACKAGE1ST];
for (shift = 0; npackage > PGFL_MAX_BUCKETS; shift++) {
npackage >>= 1;
}
uvm_page_redim(uvmexp.ncolors, npackage);
npackage = 0;
ci = curcpu();
ci2 = ci->ci_sibling[CPUREL_PACKAGE1ST];
do {
ci3 = ci2;
do {
ci3->ci_data.cpu_uvm->pgflbucket = npackage >> shift;
ci3 = ci3->ci_sibling[CPUREL_PACKAGE];
} while (ci3 != ci2);
npackage++;
ci2 = ci2->ci_sibling[CPUREL_PACKAGE1ST];
} while (ci2 != ci->ci_sibling[CPUREL_PACKAGE1ST]);
aprint_debug("UVM: using package allocation scheme, "
"%d package(s) per bucket\n", 1 << shift);
}
void
uvm_cpu_attach(struct cpu_info *ci)
{
struct uvm_cpu *ucpu;
if (!CPU_IS_PRIMARY(ci)) {
uvmexp.reserve_kernel += vm_page_reserve_kernel;
ucpu = kmem_zalloc(sizeof(struct uvm_cpu) + coherency_unit - 1,
KM_SLEEP);
ucpu = (struct uvm_cpu *)roundup2((uintptr_t)ucpu,
coherency_unit);
ci->ci_data.cpu_uvm = ucpu;
} else {
ucpu = ci->ci_data.cpu_uvm;
}
uvmpdpol_init_cpu(ucpu);
}
int
uvm_availmem(bool cached)
{
int64_t fp;
cpu_count_sync(cached);
if ((fp = cpu_count_get(CPU_COUNT_FREEPAGES)) < 0) {
fp = 0;
}
return (int)fp;
}
static struct vm_page *
uvm_pagealloc_pgb(struct uvm_cpu *ucpu, int f, int b, int *trycolorp, int flags)
{
int c, trycolor, colormask;
struct pgflbucket *pgb;
struct vm_page *pg;
kmutex_t *lock;
bool fill;
pgb = uvm.page_free[f].pgfl_buckets[b];
if (pgb->pgb_nfree == 0) {
return NULL;
}
lock = &uvm_freelist_locks[b].lock;
mutex_spin_enter(lock);
if (__predict_false(pgb->pgb_nfree <= uvmexp.reserve_kernel)) {
if ((flags & UVM_PGA_USERESERVE) == 0 ||
(pgb->pgb_nfree <= uvmexp.reserve_pagedaemon &&
!uvm_lwp_is_pagedaemon(curlwp))) {
mutex_spin_exit(lock);
return NULL;
}
fill = false;
} else {
fill = true;
}
c = trycolor = *trycolorp;
colormask = uvmexp.colormask;
do {
pg = LIST_FIRST(&pgb->pgb_colors[c]);
if (__predict_true(pg != NULL)) {
LIST_REMOVE(pg, pageq.list);
KASSERT(pg->flags == PG_FREE);
pg->flags = PG_BUSY | PG_CLEAN | PG_FAKE;
pgb->pgb_nfree--;
CPU_COUNT(CPU_COUNT_FREEPAGES, -1);
if (__predict_true(b == ucpu->pgflbucket && fill)) {
uvm_pgflcache_fill(ucpu, f, b, c);
}
mutex_spin_exit(lock);
KASSERT(uvm_page_get_bucket(pg) == b);
CPU_COUNT(c == trycolor ?
CPU_COUNT_COLORHIT : CPU_COUNT_COLORMISS, 1);
CPU_COUNT(CPU_COUNT_CPUMISS, 1);
*trycolorp = c;
return pg;
}
c = (c + 1) & colormask;
} while (c != trycolor);
mutex_spin_exit(lock);
return NULL;
}
static struct vm_page *
uvm_pagealloc_pgfl(struct uvm_cpu *ucpu, int f, int *trycolorp, int flags)
{
int b, trybucket, bucketcount;
struct vm_page *pg;
if ((pg = uvm_pgflcache_alloc(ucpu, f, *trycolorp)) != NULL) {
CPU_COUNT(CPU_COUNT_CPUHIT, 1);
CPU_COUNT(CPU_COUNT_COLORHIT, 1);
return pg;
}
trybucket = ucpu->pgflbucket;
b = trybucket;
bucketcount = uvm.bucketcount;
do {
pg = uvm_pagealloc_pgb(ucpu, f, b, trycolorp, flags);
if (pg != NULL) {
return pg;
}
b = (b + 1 == bucketcount ? 0 : b + 1);
} while (b != trybucket);
return NULL;
}
struct vm_page *
uvm_pagealloc_strat(struct uvm_object *obj, voff_t off, struct vm_anon *anon,
int flags, int strat, int free_list)
{
int color, lcv, error, s;
struct uvm_cpu *ucpu;
struct vm_page *pg;
lwp_t *l;
KASSERT(obj == NULL || anon == NULL);
KASSERT(anon == NULL || (flags & UVM_FLAG_COLORMATCH) || off == 0);
KASSERT(off == trunc_page(off));
KASSERT(obj == NULL || rw_write_held(obj->vmobjlock));
KASSERT(anon == NULL || anon->an_lock == NULL ||
rw_write_held(anon->an_lock));
s = splvm();
ucpu = curcpu()->ci_data.cpu_uvm;
if (flags & UVM_FLAG_COLORMATCH) {
color = atop(off) & uvmexp.colormask;
} else {
color = ucpu->pgflcolor;
}
l = curlwp;
if (__predict_true(l != NULL) && (l->l_flag & LW_SYSTEM) != 0) {
flags |= UVM_PGA_USERESERVE;
}
again:
switch (strat) {
case UVM_PGA_STRAT_NORMAL:
for (lcv = 0; lcv < VM_NFREELIST; lcv++) {
pg = uvm_pagealloc_pgfl(ucpu, lcv, &color, flags);
if (pg != NULL) {
goto gotit;
}
}
splx(s);
uvm_kick_pdaemon();
return NULL;
case UVM_PGA_STRAT_ONLY:
case UVM_PGA_STRAT_FALLBACK:
KASSERT(free_list >= 0);
KASSERT(free_list < VM_NFREELIST);
pg = uvm_pagealloc_pgfl(ucpu, free_list, &color, flags);
if (pg != NULL) {
goto gotit;
}
if (strat == UVM_PGA_STRAT_FALLBACK) {
strat = UVM_PGA_STRAT_NORMAL;
goto again;
}
splx(s);
uvm_kick_pdaemon();
return NULL;
case UVM_PGA_STRAT_NUMA:
for (lcv = 0; lcv < VM_NFREELIST; lcv++) {
pg = uvm_pgflcache_alloc(ucpu, lcv, color);
if (pg != NULL) {
CPU_COUNT(CPU_COUNT_CPUHIT, 1);
CPU_COUNT(CPU_COUNT_COLORHIT, 1);
goto gotit;
}
pg = uvm_pagealloc_pgb(ucpu, lcv,
ucpu->pgflbucket, &color, flags);
if (pg != NULL) {
goto gotit;
}
}
strat = UVM_PGA_STRAT_NORMAL;
goto again;
default:
panic("uvm_pagealloc_strat: bad strat %d", strat);
}
gotit:
ucpu->pgflcolor = (color + 1) & uvmexp.colormask;
if (anon) {
CPU_COUNT(CPU_COUNT_ANONCLEAN, 1);
}
splx(s);
KASSERT(pg->flags == (PG_BUSY|PG_CLEAN|PG_FAKE));
if (anon != NULL || obj != NULL) {
mutex_enter(&pg->interlock);
}
pg->offset = off;
pg->uobject = obj;
pg->uanon = anon;
KASSERT(uvm_page_owner_locked_p(pg, true));
if (anon) {
anon->an_page = pg;
pg->flags |= PG_ANON;
mutex_exit(&pg->interlock);
} else if (obj) {
if (UVM_OBJ_IS_VNODE(obj)) {
pg->flags |= PG_FILE;
} else if (UVM_OBJ_IS_AOBJ(obj)) {
pg->flags |= PG_AOBJ;
}
uvm_pageinsert_object(obj, pg);
mutex_exit(&pg->interlock);
error = uvm_pageinsert_tree(obj, pg);
if (error != 0) {
mutex_enter(&pg->interlock);
uvm_pageremove_object(obj, pg);
mutex_exit(&pg->interlock);
uvm_pagefree(pg);
return NULL;
}
}
#if defined(UVM_PAGE_TRKOWN)
pg->owner_tag = NULL;
#endif
UVM_PAGE_OWN(pg, "new alloc");
if (flags & UVM_PGA_ZERO) {
if (obj != NULL || anon != NULL) {
uvm_pagemarkdirty(pg, UVM_PAGE_STATUS_DIRTY);
}
pmap_zero_page(VM_PAGE_TO_PHYS(pg));
}
return(pg);
}
void
uvm_pagereplace(struct vm_page *oldpg, struct vm_page *newpg)
{
struct uvm_object *uobj = oldpg->uobject;
struct vm_page *pg __diagused;
uint64_t idx;
KASSERT((oldpg->flags & PG_TABLED) != 0);
KASSERT(uobj != NULL);
KASSERT((newpg->flags & PG_TABLED) == 0);
KASSERT(newpg->uobject == NULL);
KASSERT(rw_write_held(uobj->vmobjlock));
KASSERT(mutex_owned(&oldpg->interlock));
KASSERT(mutex_owned(&newpg->interlock));
newpg->uobject = uobj;
newpg->offset = oldpg->offset;
idx = newpg->offset >> PAGE_SHIFT;
pg = radix_tree_replace_node(&uobj->uo_pages, idx, newpg);
KASSERT(pg == oldpg);
if (((oldpg->flags ^ newpg->flags) & PG_CLEAN) != 0) {
if ((newpg->flags & PG_CLEAN) != 0) {
uvm_obj_page_clear_dirty(newpg);
} else {
uvm_obj_page_set_dirty(newpg);
}
}
newpg->flags |=
(newpg->flags & ~PG_STAT) | (oldpg->flags & PG_STAT);
uvm_pageinsert_object(uobj, newpg);
uvm_pageremove_object(uobj, oldpg);
}
int
uvm_pagerealloc(struct vm_page *pg, struct uvm_object *newobj, voff_t newoff)
{
int error = 0;
if (pg->uobject) {
uvm_pageremove_tree(pg->uobject, pg);
uvm_pageremove_object(pg->uobject, pg);
}
if (newobj) {
mutex_enter(&pg->interlock);
pg->uobject = newobj;
pg->offset = newoff;
if (UVM_OBJ_IS_VNODE(newobj)) {
pg->flags |= PG_FILE;
} else if (UVM_OBJ_IS_AOBJ(newobj)) {
pg->flags |= PG_AOBJ;
}
uvm_pageinsert_object(newobj, pg);
mutex_exit(&pg->interlock);
error = uvm_pageinsert_tree(newobj, pg);
if (error != 0) {
mutex_enter(&pg->interlock);
uvm_pageremove_object(newobj, pg);
mutex_exit(&pg->interlock);
}
}
return error;
}
void
uvm_pagefree(struct vm_page *pg)
{
struct pgfreelist *pgfl;
struct pgflbucket *pgb;
struct uvm_cpu *ucpu;
kmutex_t *lock;
int bucket, s;
bool locked;
#ifdef DEBUG
if (pg->uobject == (void *)0xdeadbeef &&
pg->uanon == (void *)0xdeadbeef) {
panic("uvm_pagefree: freeing free page %p", pg);
}
#endif
KASSERT((pg->flags & PG_PAGEOUT) == 0);
KASSERT(!(pg->flags & PG_FREE));
KASSERT(pg->uobject == NULL || rw_write_held(pg->uobject->vmobjlock));
KASSERT(pg->uobject != NULL || pg->uanon == NULL ||
rw_write_held(pg->uanon->an_lock));
if (pg->uobject != NULL) {
uvm_pageremove_tree(pg->uobject, pg);
}
if (pg->loan_count) {
KASSERT(pg->wire_count == 0);
uvm_pagelock(pg);
locked = true;
if (pg->uobject != NULL) {
uvm_pageremove_object(pg->uobject, pg);
pg->flags &= ~(PG_FILE|PG_AOBJ);
} else if (pg->uanon != NULL) {
if ((pg->flags & PG_ANON) == 0) {
pg->loan_count--;
} else {
const unsigned status = uvm_pagegetdirty(pg);
pg->flags &= ~PG_ANON;
cpu_count(CPU_COUNT_ANONUNKNOWN + status, -1);
}
pg->uanon->an_page = NULL;
pg->uanon = NULL;
}
if (pg->pqflags & PQ_WANTED) {
wakeup(pg);
}
pg->pqflags &= ~PQ_WANTED;
pg->flags &= ~(PG_BUSY|PG_RELEASED|PG_PAGER1);
#ifdef UVM_PAGE_TRKOWN
pg->owner_tag = NULL;
#endif
KASSERT((pg->flags & PG_STAT) == 0);
if (pg->loan_count) {
KASSERT(pg->uobject == NULL);
if (pg->uanon == NULL) {
uvm_pagedequeue(pg);
}
uvm_pageunlock(pg);
return;
}
} else if (pg->uobject != NULL || pg->uanon != NULL ||
pg->wire_count != 0) {
uvm_pagelock(pg);
locked = true;
} else {
locked = false;
}
if (pg->uobject != NULL) {
uvm_pageremove_object(pg->uobject, pg);
} else if (pg->uanon != NULL) {
const unsigned int status = uvm_pagegetdirty(pg);
pg->uanon->an_page = NULL;
pg->uanon = NULL;
cpu_count(CPU_COUNT_ANONUNKNOWN + status, -1);
}
if (pg->wire_count) {
pg->wire_count = 0;
atomic_dec_uint(&uvmexp.wired);
}
if (locked) {
if ((pg->pqflags & PQ_WANTED) != 0) {
pg->pqflags &= ~PQ_WANTED;
wakeup(pg);
}
uvm_pagedequeue(pg);
uvm_pageunlock(pg);
} else {
KASSERT(!uvmpdpol_pageisqueued_p(pg));
}
#ifdef DEBUG
pg->uobject = (void *)0xdeadbeef;
pg->uanon = (void *)0xdeadbeef;
#endif
s = splvm();
ucpu = curcpu()->ci_data.cpu_uvm;
bucket = uvm_page_get_bucket(pg);
if (bucket == ucpu->pgflbucket && uvm_pgflcache_free(ucpu, pg)) {
splx(s);
return;
}
pgfl = &uvm.page_free[uvm_page_get_freelist(pg)];
pgb = pgfl->pgfl_buckets[bucket];
lock = &uvm_freelist_locks[bucket].lock;
mutex_spin_enter(lock);
pg->flags = PG_FREE;
LIST_INSERT_HEAD(&pgb->pgb_colors[VM_PGCOLOR(pg)], pg, pageq.list);
pgb->pgb_nfree++;
CPU_COUNT(CPU_COUNT_FREEPAGES, 1);
mutex_spin_exit(lock);
splx(s);
}
void
uvm_page_unbusy(struct vm_page **pgs, int npgs)
{
struct vm_page *pg;
int i, pageout_done;
UVMHIST_FUNC(__func__); UVMHIST_CALLED(ubchist);
pageout_done = 0;
for (i = 0; i < npgs; i++) {
pg = pgs[i];
if (pg == NULL || pg == PGO_DONTCARE) {
continue;
}
KASSERT(uvm_page_owner_locked_p(pg, true));
KASSERT(pg->flags & PG_BUSY);
if (pg->flags & PG_PAGEOUT) {
pg->flags &= ~PG_PAGEOUT;
pg->flags |= PG_RELEASED;
pageout_done++;
atomic_inc_uint(&uvmexp.pdfreed);
}
if (pg->flags & PG_RELEASED) {
UVMHIST_LOG(ubchist, "releasing pg %#jx",
(uintptr_t)pg, 0, 0, 0);
KASSERT(pg->uobject != NULL ||
(pg->uanon != NULL && pg->uanon->an_ref > 0));
pg->flags &= ~PG_RELEASED;
uvm_pagefree(pg);
} else {
UVMHIST_LOG(ubchist, "unbusying pg %#jx",
(uintptr_t)pg, 0, 0, 0);
KASSERT((pg->flags & PG_FAKE) == 0);
pg->flags &= ~PG_BUSY;
uvm_pagelock(pg);
uvm_pagewakeup(pg);
uvm_pageunlock(pg);
UVM_PAGE_OWN(pg, NULL);
}
}
if (pageout_done != 0) {
uvm_pageout_done(pageout_done);
}
}
void
uvm_pagewait(struct vm_page *pg, krwlock_t *lock, const char *wmesg)
{
KASSERT(rw_lock_held(lock));
KASSERT((pg->flags & PG_BUSY) != 0);
KASSERT(uvm_page_owner_locked_p(pg, false));
mutex_enter(&pg->interlock);
pg->pqflags |= PQ_WANTED;
rw_exit(lock);
UVM_UNLOCK_AND_WAIT(pg, &pg->interlock, false, wmesg, 0);
}
void
uvm_pagewakeup(struct vm_page *pg)
{
UVMHIST_FUNC(__func__); UVMHIST_CALLED(ubchist);
KASSERT(mutex_owned(&pg->interlock));
UVMHIST_LOG(ubchist, "waking pg %#jx", (uintptr_t)pg, 0, 0, 0);
if ((pg->pqflags & PQ_WANTED) != 0) {
wakeup(pg);
pg->pqflags &= ~PQ_WANTED;
}
}
bool
uvm_pagewanted_p(struct vm_page *pg)
{
KASSERT(uvm_page_owner_locked_p(pg, true));
return (atomic_load_relaxed(&pg->pqflags) & PQ_WANTED) != 0;
}
#if defined(UVM_PAGE_TRKOWN)
void
uvm_page_own(struct vm_page *pg, const char *tag)
{
KASSERT((pg->flags & (PG_PAGEOUT|PG_RELEASED)) == 0);
KASSERT(uvm_page_owner_locked_p(pg, true));
if (tag) {
KASSERT((pg->flags & PG_BUSY) != 0);
if (pg->owner_tag) {
printf("uvm_page_own: page %p already owned "
"by proc %d.%d [%s]\n", pg,
pg->owner, pg->lowner, pg->owner_tag);
panic("uvm_page_own");
}
pg->owner = curproc->p_pid;
pg->lowner = curlwp->l_lid;
pg->owner_tag = tag;
return;
}
KASSERT((pg->flags & PG_BUSY) == 0);
if (pg->owner_tag == NULL) {
printf("uvm_page_own: dropping ownership of an non-owned "
"page (%p)\n", pg);
panic("uvm_page_own");
}
pg->owner_tag = NULL;
}
#endif
struct vm_page *
uvm_pagelookup(struct uvm_object *obj, voff_t off)
{
struct vm_page *pg;
KASSERT(db_active || rw_lock_held(obj->vmobjlock));
pg = radix_tree_lookup_node(&obj->uo_pages, off >> PAGE_SHIFT);
KASSERT(pg == NULL || obj->uo_npages != 0);
KASSERT(pg == NULL || (pg->flags & (PG_RELEASED|PG_PAGEOUT)) == 0 ||
(pg->flags & PG_BUSY) != 0);
return pg;
}
void
uvm_pagewire(struct vm_page *pg)
{
KASSERT(uvm_page_owner_locked_p(pg, true));
KASSERT(mutex_owned(&pg->interlock));
#if defined(READAHEAD_STATS)
if ((pg->flags & PG_READAHEAD) != 0) {
uvm_ra_hit.ev_count++;
pg->flags &= ~PG_READAHEAD;
}
#endif
if (pg->wire_count == 0) {
uvm_pagedequeue(pg);
atomic_inc_uint(&uvmexp.wired);
}
pg->wire_count++;
KASSERT(pg->wire_count > 0);
}
void
uvm_pageunwire(struct vm_page *pg)
{
KASSERT(uvm_page_owner_locked_p(pg, true));
KASSERT(pg->wire_count != 0);
KASSERT(!uvmpdpol_pageisqueued_p(pg));
KASSERT(mutex_owned(&pg->interlock));
pg->wire_count--;
if (pg->wire_count == 0) {
uvm_pageactivate(pg);
KASSERT(uvmexp.wired != 0);
atomic_dec_uint(&uvmexp.wired);
}
}
void
uvm_pagedeactivate(struct vm_page *pg)
{
KASSERT(uvm_page_owner_locked_p(pg, false));
KASSERT(mutex_owned(&pg->interlock));
if (pg->wire_count == 0) {
KASSERT(uvmpdpol_pageisqueued_p(pg));
uvmpdpol_pagedeactivate(pg);
}
}
void
uvm_pageactivate(struct vm_page *pg)
{
KASSERT(uvm_page_owner_locked_p(pg, false));
KASSERT(mutex_owned(&pg->interlock));
#if defined(READAHEAD_STATS)
if ((pg->flags & PG_READAHEAD) != 0) {
uvm_ra_hit.ev_count++;
pg->flags &= ~PG_READAHEAD;
}
#endif
if (pg->wire_count == 0) {
uvmpdpol_pageactivate(pg);
}
}
void
uvm_pagedequeue(struct vm_page *pg)
{
KASSERT(uvm_page_owner_locked_p(pg, true));
KASSERT(mutex_owned(&pg->interlock));
if (uvmpdpol_pageisqueued_p(pg)) {
uvmpdpol_pagedequeue(pg);
}
}
void
uvm_pageenqueue(struct vm_page *pg)
{
KASSERT(uvm_page_owner_locked_p(pg, false));
KASSERT(mutex_owned(&pg->interlock));
if (pg->wire_count == 0 && !uvmpdpol_pageisqueued_p(pg)) {
uvmpdpol_pageenqueue(pg);
}
}
void
uvm_pagelock(struct vm_page *pg)
{
mutex_enter(&pg->interlock);
}
void
uvm_pagelock2(struct vm_page *pg1, struct vm_page *pg2)
{
if (pg1 < pg2) {
mutex_enter(&pg1->interlock);
mutex_enter(&pg2->interlock);
} else {
mutex_enter(&pg2->interlock);
mutex_enter(&pg1->interlock);
}
}
void
uvm_pageunlock(struct vm_page *pg)
{
if ((pg->pqflags & PQ_INTENT_SET) == 0 ||
(pg->pqflags & PQ_INTENT_QUEUED) != 0) {
mutex_exit(&pg->interlock);
return;
}
pg->pqflags |= PQ_INTENT_QUEUED;
mutex_exit(&pg->interlock);
uvmpdpol_pagerealize(pg);
}
void
uvm_pageunlock2(struct vm_page *pg1, struct vm_page *pg2)
{
if ((pg1->pqflags & PQ_INTENT_SET) == 0 ||
(pg1->pqflags & PQ_INTENT_QUEUED) != 0) {
mutex_exit(&pg1->interlock);
pg1 = NULL;
} else {
pg1->pqflags |= PQ_INTENT_QUEUED;
mutex_exit(&pg1->interlock);
}
if ((pg2->pqflags & PQ_INTENT_SET) == 0 ||
(pg2->pqflags & PQ_INTENT_QUEUED) != 0) {
mutex_exit(&pg2->interlock);
pg2 = NULL;
} else {
pg2->pqflags |= PQ_INTENT_QUEUED;
mutex_exit(&pg2->interlock);
}
if (pg1 != NULL) {
uvmpdpol_pagerealize(pg1);
}
if (pg2 != NULL) {
uvmpdpol_pagerealize(pg2);
}
}
void
uvm_pagezero(struct vm_page *pg)
{
uvm_pagemarkdirty(pg, UVM_PAGE_STATUS_DIRTY);
pmap_zero_page(VM_PAGE_TO_PHYS(pg));
}
void
uvm_pagecopy(struct vm_page *src, struct vm_page *dst)
{
uvm_pagemarkdirty(dst, UVM_PAGE_STATUS_DIRTY);
pmap_copy_page(VM_PAGE_TO_PHYS(src), VM_PAGE_TO_PHYS(dst));
}
bool
uvm_pageismanaged(paddr_t pa)
{
return (uvm_physseg_find(atop(pa), NULL) != UVM_PHYSSEG_TYPE_INVALID);
}
int
uvm_page_lookup_freelist(struct vm_page *pg)
{
uvm_physseg_t upm;
upm = uvm_physseg_find(atop(VM_PAGE_TO_PHYS(pg)), NULL);
KASSERT(upm != UVM_PHYSSEG_TYPE_INVALID);
return uvm_physseg_get_free_list(upm);
}
bool
uvm_page_owner_locked_p(struct vm_page *pg, bool exclusive)
{
if (pg->uobject != NULL) {
return exclusive
? rw_write_held(pg->uobject->vmobjlock)
: rw_lock_held(pg->uobject->vmobjlock);
}
if (pg->uanon != NULL) {
return exclusive
? rw_write_held(pg->uanon->an_lock)
: rw_lock_held(pg->uanon->an_lock);
}
return true;
}
bool
uvm_pagereadonly_p(struct vm_page *pg)
{
struct uvm_object * const uobj = pg->uobject;
KASSERT(uobj == NULL || rw_lock_held(uobj->vmobjlock));
KASSERT(uobj != NULL || rw_lock_held(pg->uanon->an_lock));
if ((pg->flags & PG_RDONLY) != 0) {
return true;
}
if (uvm_pagegetdirty(pg) == UVM_PAGE_STATUS_CLEAN) {
return true;
}
if (uobj == NULL) {
return false;
}
return UVM_OBJ_NEEDS_WRITEFAULT(uobj);
}
#ifdef PMAP_DIRECT
int
uvm_direct_process(struct vm_page **pgs, u_int npages, voff_t off, vsize_t len,
int (*process)(void *, size_t, void *), void *arg)
{
int error = 0;
paddr_t pa;
size_t todo;
voff_t pgoff = (off & PAGE_MASK);
struct vm_page *pg;
KASSERT(npages > 0);
KASSERT(len > 0);
for (int i = 0; i < npages; i++) {
pg = pgs[i];
KASSERT(len > 0);
KASSERT(pg != NULL);
KASSERT(pg != PGO_DONTCARE);
pa = VM_PAGE_TO_PHYS(pg);
todo = MIN(len, PAGE_SIZE - pgoff);
error = pmap_direct_process(pa, pgoff, todo, process, arg);
if (error)
break;
pgoff = 0;
len -= todo;
}
KASSERTMSG(error != 0 || len == 0, "len %lu != 0 for non-error", len);
return error;
}
#endif
#if defined(DDB) || defined(DEBUGPRINT)
static const char page_flagbits[] = UVM_PGFLAGBITS;
static const char page_pqflagbits[] = UVM_PQFLAGBITS;
void
uvm_page_printit(struct vm_page *pg, bool full,
void (*pr)(const char *, ...))
{
struct vm_page *tpg;
struct uvm_object *uobj;
struct pgflbucket *pgb;
struct pgflist *pgl;
char pgbuf[128];
(*pr)("PAGE %p:\n", pg);
snprintb(pgbuf, sizeof(pgbuf), page_flagbits, pg->flags);
(*pr)(" flags=%s\n", pgbuf);
snprintb(pgbuf, sizeof(pgbuf), page_pqflagbits, pg->pqflags);
(*pr)(" pqflags=%s\n", pgbuf);
(*pr)(" uobject=%p, uanon=%p, offset=0x%llx\n",
pg->uobject, pg->uanon, (long long)pg->offset);
(*pr)(" loan_count=%d wire_count=%d bucket=%d freelist=%d\n",
pg->loan_count, pg->wire_count, uvm_page_get_bucket(pg),
uvm_page_get_freelist(pg));
(*pr)(" pa=0x%lx\n", (long)VM_PAGE_TO_PHYS(pg));
#if defined(UVM_PAGE_TRKOWN)
if (pg->flags & PG_BUSY)
(*pr)(" owning process = %d.%d, tag=%s\n",
pg->owner, pg->lowner, pg->owner_tag);
else
(*pr)(" page not busy, no owner\n");
#else
(*pr)(" [page ownership tracking disabled]\n");
#endif
if (!full)
return;
if ((pg->flags & PG_FREE) == 0) {
if (pg->flags & PG_ANON) {
if (pg->uanon == NULL || pg->uanon->an_page != pg)
(*pr)(" >>> ANON DOES NOT POINT HERE <<< (%p)\n",
(pg->uanon) ? pg->uanon->an_page : NULL);
else
(*pr)(" anon backpointer is OK\n");
} else {
uobj = pg->uobject;
if (uobj) {
(*pr)(" checking object list\n");
tpg = uvm_pagelookup(uobj, pg->offset);
if (tpg)
(*pr)(" page found on object list\n");
else
(*pr)(" >>> PAGE NOT FOUND ON OBJECT LIST! <<<\n");
}
}
}
if (pg->flags & PG_FREE) {
int fl = uvm_page_get_freelist(pg);
int b = uvm_page_get_bucket(pg);
pgb = uvm.page_free[fl].pgfl_buckets[b];
pgl = &pgb->pgb_colors[VM_PGCOLOR(pg)];
(*pr)(" checking pageq list\n");
LIST_FOREACH(tpg, pgl, pageq.list) {
if (tpg == pg) {
break;
}
}
if (tpg)
(*pr)(" page found on pageq list\n");
else
(*pr)(" >>> PAGE NOT FOUND ON PAGEQ LIST! <<<\n");
}
}
void
uvm_page_printall(void (*pr)(const char *, ...))
{
uvm_physseg_t i;
paddr_t pfn;
struct vm_page *pg;
(*pr)("%18s %4s %4s %18s %18s"
#ifdef UVM_PAGE_TRKOWN
" OWNER"
#endif
"\n", "PAGE", "FLAG", "PQ", "UOBJECT", "UANON");
for (i = uvm_physseg_get_first();
uvm_physseg_valid_p(i);
i = uvm_physseg_get_next(i)) {
for (pfn = uvm_physseg_get_start(i);
pfn < uvm_physseg_get_end(i);
pfn++) {
pg = PHYS_TO_VM_PAGE(ptoa(pfn));
(*pr)("%18p %04x %08x %18p %18p",
pg, pg->flags, pg->pqflags, pg->uobject,
pg->uanon);
#ifdef UVM_PAGE_TRKOWN
if (pg->flags & PG_BUSY)
(*pr)(" %d [%s]", pg->owner, pg->owner_tag);
#endif
(*pr)("\n");
}
}
}
void
uvm_page_print_freelists(void (*pr)(const char *, ...))
{
struct pgfreelist *pgfl;
struct pgflbucket *pgb;
int fl, b, c;
(*pr)("There are %d freelists with %d buckets of %d colors.\n\n",
VM_NFREELIST, uvm.bucketcount, uvmexp.ncolors);
for (fl = 0; fl < VM_NFREELIST; fl++) {
pgfl = &uvm.page_free[fl];
(*pr)("freelist(%d) @ %p\n", fl, pgfl);
for (b = 0; b < uvm.bucketcount; b++) {
pgb = uvm.page_free[fl].pgfl_buckets[b];
(*pr)(" bucket(%d) @ %p, nfree = %d, lock @ %p:\n",
b, pgb, pgb->pgb_nfree,
&uvm_freelist_locks[b].lock);
for (c = 0; c < uvmexp.ncolors; c++) {
(*pr)(" color(%d) @ %p, ", c,
&pgb->pgb_colors[c]);
(*pr)("first page = %p\n",
LIST_FIRST(&pgb->pgb_colors[c]));
}
}
}
}
#endif