/* * Copyright (c) 2000-2006 Apple Computer, Inc. All rights reserved. * * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. The rights granted to you under the License * may not be used to create, or enable the creation or redistribution of, * unlawful or unlicensed copies of an Apple operating system, or to * circumvent, violate, or enable the circumvention or violation of, any * terms of an Apple operating system software license agreement. * * Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ */ /* * This file is used to maintain the exception save areas * */ #include <debug.h> #include <mach_kgdb.h> #include <mach_vm_debug.h> #include <kern/thread.h> #include <mach/vm_attributes.h> #include <mach/vm_param.h> #include <vm/vm_kern.h> #include <vm/vm_map.h> #include <vm/vm_page.h> #include <mach/ppc/thread_status.h> #include <kern/spl.h> #include <kern/simple_lock.h> #include <kern/misc_protos.h> #include <ppc/misc_protos.h> #include <ppc/proc_reg.h> #include <ppc/mem.h> #include <ppc/pmap.h> #include <ppc/Firmware.h> #include <ppc/mappings.h> #include <ppc/exception.h> #include <ppc/savearea.h> #include <ddb/db_output.h> struct Saveanchor backpocket; /* Emergency saveareas */ unsigned int debsave0 = 0; /* Debug flag */ unsigned int backchain = 0; /* Debug flag */ /* * These routines keep track of exception save areas and keeps the count within specific limits. If there are * too few, more are allocated, too many, and they are released. This savearea is where the PCBs are * stored. They never span a page boundary and are referenced by both virtual and real addresses. * Within the interrupt vectors, the real address is used because at that level, no exceptions * can be tolerated. Save areas can be dynamic or permanent. Permanant saveareas are allocated * at boot time and must be in place before any type of exception occurs. These are never released, * and the number is based upon some arbitrary (yet to be determined) amount times the number of * processors. This represents the minimum number required to process a total system failure without * destroying valuable and ever-so-handy system debugging information. * * We keep two global free lists (the savearea free pool and the savearea free list) and one local * list per processor. * * The local lists are small and require no locked access. They are chained using physical addresses * and no interruptions are allowed when adding to or removing from the list. Also known as the * qfret list. This list is local to a processor and is intended for use only by very low level * context handling code. * * The savearea free list is a medium size list that is globally accessible. It is updated * while holding a simple lock. The length of time that the lock is held is kept short. The * longest period of time is when the list is trimmed. Like the qfret lists, this is chained physically * and must be accessed with translation and interruptions disabled. This is where the bulk * of the free entries are located. * * The saveareas are allocated from full pages. A pool element is marked * with an allocation map that shows which "slots" are free. These pages are allocated via the * normal kernel memory allocation functions. Queueing is with physical addresses. The enqueue, * dequeue, and search for free blocks is done under free list lock. * only if there are empty slots in it. * * Saveareas that are counted as "in use" once they are removed from the savearea free list. * This means that all areas on the local qfret list are considered in use. * * There are two methods of obtaining a savearea. The save_get function (which is also inlined * in the low-level exception handler) attempts to get an area from the local qfret list. This is * done completely without locks. If qfret is exahusted (or maybe just too low) an area is allocated * from the savearea free list. If the free list is empty, we install the back pocket areas and * panic. * * The save_alloc function is designed to be called by high level routines, e.g., thread creation, * etc. It will allocate from the free list. After allocation, it will compare the free count * to the target value. If outside of the range, it will adjust the size either upwards or * downwards. * * If we need to shrink the list, it will be trimmed to the target size and unlocked. The code * will walk the chain and return each savearea to its pool page. If a pool page becomes * completely empty, it is dequeued from the free pool list and enqueued (atomic queue * function) to be released. * * Once the trim list is finished, the pool release queue is checked to see if there are pages * waiting to be released. If so, they are released one at a time. * * If the free list needed to be grown rather than shrunken, we will first attempt to recover * a page from the pending release queue (built when we trim the free list). If we find one, * it is allocated, otherwise, a page of kernel memory is allocated. This loops until there are * enough free saveareas. * */ /* * Allocate our initial context save areas. As soon as we do this, * we can take an interrupt. We do the saveareas here, 'cause they're guaranteed * to be at least page aligned. * * Note: these initial saveareas are all to be allocated from V=R, less than 4GB * space. */ void savearea_init(vm_offset_t addr) { savearea_comm *savec; vm_offset_t save; unsigned int i; saveanchor.savetarget = InitialSaveTarget; /* Initial target value */ saveanchor.saveinuse = 0; /* Number of areas in use */ saveanchor.savefree = 0; /* Remember the start of the free chain */ saveanchor.savefreecnt = 0; /* Remember the length */ saveanchor.savepoolfwd = (addr64_t)(uintptr_t)&saveanchor; /* Remember pool forward */ saveanchor.savepoolbwd = (addr64_t)(uintptr_t)&saveanchor; /* Remember pool backward */ save = addr; /* Point to the whole block of blocks */ /* * First we allocate the back pocket in case of emergencies */ for(i=0; i < BackPocketSaveBloks; i++) { /* Initialize the back pocket saveareas */ savec = (savearea_comm *)save; /* Get the control area for this one */ savec->sac_alloc = 0; /* Mark it allocated */ savec->sac_vrswap = 0; /* V=R, so the translation factor is 0 */ savec->sac_flags = sac_perm; /* Mark it permanent */ savec->sac_flags |= 0x0000EE00; /* Debug eyecatcher */ save_queue((uint32_t)savec >> 12); /* Add page to savearea lists */ save += PAGE_SIZE; /* Jump up to the next one now */ } backpocket = saveanchor; /* Save this for emergencies */ /* * We've saved away the back pocket savearea info, so reset it all and * now allocate for real */ saveanchor.savefree = 0; /* Remember the start of the free chain */ saveanchor.savefreecnt = 0; /* Remember the length */ saveanchor.saveadjust = 0; /* Set none needed yet */ saveanchor.savepoolfwd = (addr64_t)(uintptr_t)&saveanchor; /* Remember pool forward */ saveanchor.savepoolbwd = (addr64_t)(uintptr_t)&saveanchor; /* Remember pool backward */ for(i=0; i < InitialSaveBloks; i++) { /* Initialize the saveareas */ savec = (savearea_comm *)save; /* Get the control area for this one */ savec->sac_alloc = 0; /* Mark it allocated */ savec->sac_vrswap = 0; /* V=R, so the translation factor is 0 */ savec->sac_flags = sac_perm; /* Mark it permanent */ savec->sac_flags |= 0x0000EE00; /* Debug eyecatcher */ save_queue((uint32_t)savec >> 12); /* Add page to savearea lists */ save += PAGE_SIZE; /* Jump up to the next one now */ } /* * We now have a free list that has our initial number of entries * The local qfret lists is empty. When we call save_get below it will see that * the local list is empty and fill it for us. * * It is ok to call save_get here because all initial saveareas are V=R in less * than 4GB space, so 32-bit addressing is ok. * */ /* * This will populate the local list and get the first one for the system */ /* XXX next_savearea should be a void * 4425541 */ getPerProc()->next_savearea = (unsigned long)(void *)save_get(); /* * The system is now able to take interruptions */ } /* * Obtains a savearea. If the free list needs size adjustment it happens here. * Don't actually allocate the savearea until after the adjustment is done. */ struct savearea *save_alloc(void) { /* Reserve a save area */ if(saveanchor.saveadjust) save_adjust(); /* If size need adjustment, do it now */ return save_get(); /* Pass the baby... */ } /* * This routine releases a save area to the free queue. If after that, * we have more than our maximum target, we start releasing what we can * until we hit the normal target. */ void save_release(struct savearea *save) { /* Return a savearea to the free list */ save_ret(save); /* Adjust the savearea free list and pool size if needed */ if(saveanchor.saveadjust) save_adjust(); } /* * Adjusts the size of the free list. Can either release or allocate full pages * of kernel memory. This can block. * * Note that we will only run one adjustment and the amount needed may change * while we are executing. * * Calling this routine is triggered by saveanchor.saveadjust. This value is always calculated just before * we unlock the saveanchor lock (this keeps it pretty accurate). If the total of savefreecnt and saveinuse * is within the hysteresis range, it is set to 0. If outside, it is set to the number needed to bring * the total to the target value. Note that there is a minimum size to the free list (FreeListMin) and if * savefreecnt falls below that, saveadjust is set to the number needed to bring it to that. */ void save_adjust(void) { savearea_comm *sctl, *sctlnext, *freepage; kern_return_t ret; ppnum_t physpage; if(saveanchor.saveadjust < 0) { /* Do we need to adjust down? */ sctl = (savearea_comm *)save_trim_free(); /* Trim list to the need count, return start of trim list */ while(sctl) { /* Release the free pages back to the kernel */ sctlnext = CAST_DOWN(savearea_comm *, sctl->save_prev); /* Get next in list */ kmem_free(kernel_map, (vm_offset_t) sctl, PAGE_SIZE); /* Release the page */ sctl = sctlnext; /* Chain onwards */ } } else { /* We need more... */ if(save_recover()) return; /* If we can recover enough from the pool, return */ while(saveanchor.saveadjust > 0) { /* Keep going until we have enough */ ret = kmem_alloc_wired(kernel_map, (vm_offset_t *)&freepage, PAGE_SIZE); /* Get a page for free pool */ if(ret != KERN_SUCCESS) { /* Did we get some memory? */ panic("Whoops... Not a bit of wired memory left for saveareas\n"); } physpage = pmap_find_phys(kernel_pmap, (vm_offset_t)freepage); /* Find physical page */ if(!physpage) { /* See if we actually have this mapped*/ panic("save_adjust: wired page not mapped - va = %p\n", freepage); /* Die */ } bzero((void *)freepage, PAGE_SIZE); /* Clear it all to zeros */ freepage->sac_alloc = 0; /* Mark all entries taken */ freepage->sac_vrswap = ((uint64_t)physpage << 12) ^ (uint64_t)((uintptr_t)freepage); /* XOR to calculate conversion mask */ freepage->sac_flags |= 0x0000EE00; /* Set debug eyecatcher */ save_queue(physpage); /* Add all saveareas on page to free list */ } } } /* * Fake up information to make the saveareas look like a zone */ void save_fake_zone_info(int *count, vm_size_t *cur_size, vm_size_t *max_size, vm_size_t *elem_size, vm_size_t *alloc_size, int *collectable, int *exhaustable) { *count = saveanchor.saveinuse; *cur_size = (saveanchor.savefreecnt + saveanchor.saveinuse) * (PAGE_SIZE / sac_cnt); *max_size = saveanchor.savemaxcount * (PAGE_SIZE / sac_cnt); *elem_size = sizeof(struct savearea); *alloc_size = PAGE_SIZE; *collectable = 1; *exhaustable = 0; }