/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" #include <sys/types.h> #include <sys/param.h> #include <sys/sysmacros.h> #include <sys/signal.h> #include <sys/systm.h> #include <sys/user.h> #include <sys/mman.h> #include <sys/class.h> #include <sys/proc.h> #include <sys/procfs.h> #include <sys/kmem.h> #include <sys/cred.h> #include <sys/archsystm.h> #include <sys/machsystm.h> #include <sys/reboot.h> #include <sys/uadmin.h> #include <sys/vfs.h> #include <sys/vnode.h> #include <sys/session.h> #include <sys/ucontext.h> #include <sys/dnlc.h> #include <sys/var.h> #include <sys/cmn_err.h> #include <sys/debug.h> #include <sys/thread.h> #include <sys/vtrace.h> #include <sys/consdev.h> #include <sys/frame.h> #include <sys/stack.h> #include <sys/swap.h> #include <sys/vmparam.h> #include <sys/cpuvar.h> #include <sys/privregs.h> #include <vm/hat.h> #include <vm/anon.h> #include <vm/as.h> #include <vm/page.h> #include <vm/seg.h> #include <vm/seg_kmem.h> #include <vm/seg_map.h> #include <vm/seg_vn.h> #include <sys/exec.h> #include <sys/acct.h> #include <sys/modctl.h> #include <sys/tuneable.h> #include <c2/audit.h> #include <sys/trap.h> #include <sys/sunddi.h> #include <sys/bootconf.h> #include <sys/memlist.h> #include <sys/memlist_plat.h> #include <sys/systeminfo.h> #include <sys/promif.h> #include <sys/prom_plat.h> u_longlong_t spec_hole_start = 0x80000000000ull; u_longlong_t spec_hole_end = 0xfffff80000000000ull; pgcnt_t num_phys_pages() { pgcnt_t npages = 0; struct memlist *mp; for (mp = phys_install; mp != NULL; mp = mp->next) npages += mp->size >> PAGESHIFT; return (npages); } pgcnt_t size_virtalloc(prom_memlist_t *avail, size_t nelems) { u_longlong_t start, end; pgcnt_t allocpages = 0; uint_t hole_allocated = 0; uint_t i; for (i = 0; i < nelems - 1; i++) { start = avail[i].addr + avail[i].size; end = avail[i + 1].addr; /* * Notes: * * (1) OBP on platforms with US I/II pre-allocates the hole * represented by [spec_hole_start, spec_hole_end); * pre-allocation is done to make this range unavailable * for any allocation. * * (2) OBP on starcat always pre-allocates the hole similar to * platforms with US I/II. * * (3) OBP on serengeti does _not_ pre-allocate the hole. * * (4) OBP ignores Spitfire Errata #21; i.e. it does _not_ * fill up or pre-allocate an additional 4GB on both sides * of the hole. * * (5) kernel virtual range [spec_hole_start, spec_hole_end) * is _not_ used on any platform including those with * UltraSPARC III where there is no hole. * * Algorithm: * * Check if range [spec_hole_start, spec_hole_end) is * pre-allocated by OBP; if so, subtract that range from * allocpages. */ if (end >= spec_hole_end && start <= spec_hole_start) hole_allocated = 1; allocpages += btopr(end - start); } if (hole_allocated) allocpages -= btop(spec_hole_end - spec_hole_start); return (allocpages); } /* * Returns the max contiguous physical memory present in the * memlist "physavail". */ uint64_t get_max_phys_size( struct memlist *physavail) { uint64_t max_size = 0; for (; physavail; physavail = physavail->next) { if (physavail->size > max_size) max_size = physavail->size; } return (max_size); } struct vnode prom_ppages; static void more_pages(uint64_t base, uint64_t len) { void kphysm_add(); kphysm_add(base, len, 1); } static void less_pages(uint64_t base, uint64_t len) { uint64_t pa, end = base + len; extern int kcage_on; for (pa = base; pa < end; pa += PAGESIZE) { pfn_t pfnum; page_t *pp; pfnum = (pfn_t)(pa >> PAGESHIFT); if ((pp = page_numtopp_nolock(pfnum)) == NULL) cmn_err(CE_PANIC, "missing pfnum %lx", pfnum); /* * must break up any large pages that may have * constituent pages being utilized for * prom_alloc()'s. page_reclaim() can't handle * large pages. */ if (pp->p_szc != 0) page_boot_demote(pp); if (!PAGE_LOCKED(pp) && pp->p_lckcnt == 0) { /* * Ahhh yes, a prom page, * suck it off the freelist, * lock it, and hashin on prom_pages vp. */ if (page_trylock(pp, SE_EXCL) == 0) cmn_err(CE_PANIC, "prom page locked"); (void) page_reclaim(pp, NULL); /* * vnode offsets on the prom_ppages vnode * are page numbers (gack) for >32 bit * physical memory machines. */ (void) page_hashin(pp, &prom_ppages, (offset_t)pfnum, NULL); if (kcage_on) { ASSERT(pp->p_szc == 0); PP_SETNORELOC(pp); } (void) page_pp_lock(pp, 0, 1); } } } void diff_memlists(struct memlist *proto, struct memlist *diff, void (*func)()) { uint64_t p_base, p_end, d_base, d_end; while (proto != NULL) { /* * find diff item which may overlap with proto item * if none, apply func to all of proto item */ while (diff != NULL && proto->address >= diff->address + diff->size) diff = diff->next; if (diff == NULL) { (*func)(proto->address, proto->size); proto = proto->next; continue; } if (proto->address == diff->address && proto->size == diff->size) { proto = proto->next; diff = diff->next; continue; } p_base = proto->address; p_end = p_base + proto->size; d_base = diff->address; d_end = d_base + diff->size; /* * here p_base < d_end * there are 5 cases */ /* * d_end * d_base * p_end * p_base * * apply func to all of proto item */ if (p_end <= d_base) { (*func)(p_base, proto->size); proto = proto->next; continue; } /* * ... * d_base * p_base * * normalize by applying func from p_base to d_base */ if (p_base < d_base) (*func)(p_base, d_base - p_base); if (p_end <= d_end) { /* * d_end * p_end * d_base * p_base * * -or- * * d_end * p_end * p_base * d_base * * any non-overlapping ranges applied above, * so just continue */ proto = proto->next; continue; } /* * p_end * d_end * d_base * p_base * * -or- * * p_end * d_end * p_base * d_base * * Find overlapping d_base..d_end ranges, and apply func * where no overlap occurs. Stop when d_base is above * p_end */ for (p_base = d_end, diff = diff->next; diff != NULL; p_base = d_end, diff = diff->next) { d_base = diff->address; d_end = d_base + diff->size; if (p_end <= d_base) { (*func)(p_base, p_end - p_base); break; } else (*func)(p_base, d_base - p_base); } if (diff == NULL) (*func)(p_base, p_end - p_base); proto = proto->next; } } void sync_memlists(struct memlist *orig, struct memlist *new) { /* * Find pages allocated via prom by looking for * pages on orig, but no on new. */ diff_memlists(orig, new, less_pages); /* * Find pages free'd via prom by looking for * pages on new, but not on orig. */ diff_memlists(new, orig, more_pages); } /* * Find the page number of the highest installed physical * page and the number of pages installed (one cannot be * calculated from the other because memory isn't necessarily * contiguous). */ void installed_top_size_memlist_array( prom_memlist_t *list, /* base of array */ size_t nelems, /* number of elements */ pfn_t *topp, /* return ptr for top value */ pgcnt_t *sumpagesp) /* return prt for sum of installed pages */ { pfn_t top = 0; pgcnt_t sumpages = 0; pfn_t highp; /* high page in a chunk */ size_t i; for (i = 0; i < nelems; list++, i++) { highp = (list->addr + list->size - 1) >> PAGESHIFT; if (top < highp) top = highp; sumpages += (list->size >> PAGESHIFT); } *topp = top; *sumpagesp = sumpages; } /* * Copy a memory list. Used in startup() to copy boot's * memory lists to the kernel. */ void copy_memlist( prom_memlist_t *src, size_t nelems, struct memlist **dstp) { struct memlist *dst, *prev; size_t i; dst = *dstp; prev = dst; for (i = 0; i < nelems; src++, i++) { dst->address = src->addr; dst->size = src->size; dst->next = 0; if (prev == dst) { dst->prev = 0; dst++; } else { dst->prev = prev; prev->next = dst; dst++; prev++; } } *dstp = dst; } static struct bootmem_props { prom_memlist_t *ptr; size_t nelems; /* actual number of elements */ size_t maxsize; /* max buffer */ } bootmem_props[3]; #define PHYSINSTALLED 0 #define PHYSAVAIL 1 #define VIRTAVAIL 2 /* * Comapct contiguous memory list elements */ static void compact_promlist(struct bootmem_props *bpp) { int i = 0, j; struct prom_memlist *pmp = bpp->ptr; for (;;) { if (pmp[i].addr + pmp[i].size == pmp[i+1].addr) { pmp[i].size += pmp[i+1].size; bpp->nelems--; for (j = i + 1; j < bpp->nelems; j++) pmp[j] = pmp[j+1]; pmp[j].addr = 0; } else i++; if (i == bpp->nelems) break; } } /* * Sort prom memory lists into ascending order */ static void sort_promlist(struct bootmem_props *bpp) { int i, j, min; struct prom_memlist *pmp = bpp->ptr; struct prom_memlist temp; for (i = 0; i < bpp->nelems; i++) { min = i; for (j = i+1; j < bpp->nelems; j++) { if (pmp[j].addr < pmp[min].addr) min = j; } if (i != min) { /* Swap pmp[i] and pmp[min] */ temp = pmp[min]; pmp[min] = pmp[i]; pmp[i] = temp; } } } static int max_bootlist_sz; void init_boot_memlists(void) { size_t size, len; char *start; struct bootmem_props *tmp; /* * These lists can get fragmented as the prom allocates * memory, so generously round up. */ size = prom_phys_installed_len() + prom_phys_avail_len() + prom_virt_avail_len(); size *= 4; size = roundup(size, PAGESIZE); start = prom_alloc(0, size, BO_NO_ALIGN); /* * Get physinstalled */ tmp = &bootmem_props[PHYSINSTALLED]; len = prom_phys_installed_len(); if (len == 0) panic("no \"reg\" in /memory"); tmp->nelems = len / sizeof (struct prom_memlist); tmp->maxsize = len; tmp->ptr = (prom_memlist_t *)start; start += len; size -= len; (void) prom_phys_installed((caddr_t)tmp->ptr); sort_promlist(tmp); compact_promlist(tmp); /* * Start out giving each half of available space */ max_bootlist_sz = size; len = size / 2; tmp = &bootmem_props[PHYSAVAIL]; tmp->maxsize = len; tmp->ptr = (prom_memlist_t *)start; start += len; tmp = &bootmem_props[VIRTAVAIL]; tmp->maxsize = len; tmp->ptr = (prom_memlist_t *)start; } void copy_boot_memlists( prom_memlist_t **physinstalled, size_t *physinstalled_len, prom_memlist_t **physavail, size_t *physavail_len, prom_memlist_t **virtavail, size_t *virtavail_len) { size_t plen, vlen, move = 0; struct bootmem_props *il, *pl, *vl; plen = prom_phys_avail_len(); if (plen == 0) panic("no \"available\" in /memory"); vlen = prom_virt_avail_len(); if (vlen == 0) panic("no \"available\" in /virtual-memory"); if (plen + vlen > max_bootlist_sz) panic("ran out of prom_memlist space"); pl = &bootmem_props[PHYSAVAIL]; vl = &bootmem_props[VIRTAVAIL]; /* * re-adjust ptrs if needed */ if (plen > pl->maxsize) { /* move virt avail up */ move = plen - pl->maxsize; pl->maxsize = plen; vl->ptr += move / sizeof (struct prom_memlist); vl->maxsize -= move; } else if (vlen > vl->maxsize) { /* move virt avail down */ move = vlen - vl->maxsize; vl->maxsize = vlen; vl->ptr -= move / sizeof (struct prom_memlist); pl->maxsize -= move; } pl->nelems = plen / sizeof (struct prom_memlist); vl->nelems = vlen / sizeof (struct prom_memlist); /* now we can retrieve the properties */ (void) prom_phys_avail((caddr_t)pl->ptr); (void) prom_virt_avail((caddr_t)vl->ptr); /* .. and sort them */ sort_promlist(pl); sort_promlist(vl); il = &bootmem_props[PHYSINSTALLED]; *physinstalled = il->ptr; *physinstalled_len = il->nelems; *physavail = pl->ptr; *physavail_len = pl->nelems; *virtavail = vl->ptr; *virtavail_len = vl->nelems; } /* * Find the page number of the highest installed physical * page and the number of pages installed (one cannot be * calculated from the other because memory isn't necessarily * contiguous). */ void installed_top_size( struct memlist *list, /* pointer to start of installed list */ pfn_t *topp, /* return ptr for top value */ pgcnt_t *sumpagesp) /* return prt for sum of installed pages */ { pfn_t top = 0; pfn_t highp; /* high page in a chunk */ pgcnt_t sumpages = 0; for (; list; list = list->next) { highp = (list->address + list->size - 1) >> PAGESHIFT; if (top < highp) top = highp; sumpages += (uint_t)(list->size >> PAGESHIFT); } *topp = top; *sumpagesp = sumpages; }