/* @(#)vm_as.c 1.1 92/07/30 SMI */ /* * Copyright (c) 1988, 1989 by Sun Microsystems, Inc. */ /* * VM - address spaces. */ #include #include #include #include #include #include #include #include #include /* * Variables for maintaining the free list of address space structures. */ static struct as *as_freelist; static int as_freeincr = 8; /* * Find a segment containing addr. as->a_seglast is used as a * cache to remember the last segment hit we had here. We * first check to see if seglast is another hit, and if not we * determine whether to start from the head of the segment list * (as->a_segs) or from seglast and in which direction to search. */ struct seg * as_segat(as, addr) register struct as *as; register addr_t addr; { register struct seg *seg, *sseg; register forward; if (as->a_segs == NULL) /* address space has no segments */ return (NULL); if (as->a_seglast == NULL) as->a_seglast = as->a_segs; seg = as->a_seglast; forward = 0; if (seg->s_base <= addr) { if (addr < (seg->s_base + seg->s_size)) return (seg); /* seglast contained addr */ sseg = as->a_segs->s_prev; if ((addr - seg->s_base) > ((sseg->s_base + sseg->s_size) - addr)) { seg = sseg; sseg = as->a_seglast; } else { seg = as->a_seglast->s_next; sseg = as->a_segs; forward++; } } else { if ((addr - as->a_segs->s_base) > (seg->s_base - addr)) { seg = seg->s_prev; sseg = as->a_segs->s_prev; } else { sseg = seg; seg = as->a_segs; forward++; } } do { if (seg->s_base <= addr && addr < (seg->s_base + seg->s_size)) { as->a_seglast = seg; return (seg); } if (forward) { seg = seg->s_next; if (seg->s_base > addr) break; } else { seg = seg->s_prev; if (addr > (seg->s_base + seg->s_size)) break; } } while (seg != sseg); return (NULL); } /* * Allocate and initialize an address space data structure. * We call hat_alloc to allow any machine dependent * information in the hat structure to be initialized. */ struct as * as_alloc() { struct as *as; as = (struct as *)new_kmem_fast_alloc((caddr_t *)&as_freelist, sizeof (*as_freelist), as_freeincr, KMEM_SLEEP); bzero((caddr_t)as, sizeof (*as)); hat_alloc(as); return (as); } /* * Free an address space data structure. * Need to free the hat first and then * all the segments on this as and finally * the space for the as struct itself. */ void as_free(as) struct as *as; { hat_free(as); while (as->a_segs != NULL) seg_free(as->a_segs); kmem_fast_free((caddr_t *)&as_freelist, (caddr_t)as); } struct as * as_dup(as) register struct as *as; { register struct as *newas; register struct seg *seg, *sseg, *newseg; newas = as_alloc(); sseg = seg = as->a_segs; if (seg != NULL) { do { newseg = seg_alloc(newas, seg->s_base, seg->s_size); if (newseg == NULL) { as_free(newas); return (NULL); } if ((*seg->s_ops->dup)(seg, newseg)) { as_free(newas); return (NULL); } seg = seg->s_next; } while (seg != sseg); } return (newas); } /* * Add a new segment to the address space, sorting * it into the proper place in the linked list. */ enum as_res as_addseg(as, new) register struct as *as; register struct seg *new; { register struct seg *seg; register addr_t base; seg = as->a_segs; if (seg == NULL) { new->s_next = new->s_prev = new; as->a_segs = new; } else { /* * Figure out where to add the segment to keep list sorted */ base = new->s_base; do { if (base < seg->s_base) { if (base + new->s_size > seg->s_base) return (A_BADADDR); break; } if (base < seg->s_base + seg->s_size) return (A_BADADDR); seg = seg->s_next; } while (seg != as->a_segs); new->s_next = seg; new->s_prev = seg->s_prev; seg->s_prev = new; new->s_prev->s_next = new; if (base < as->a_segs->s_base) as->a_segs = new; /* new is at front */ } return (A_SUCCESS); } /* * Handle a ``fault'' at addr for size bytes. */ faultcode_t as_fault(as, addr, size, type, rw) struct as *as; addr_t addr; u_int size; enum fault_type type; enum seg_rw rw; { register struct seg *seg; register addr_t raddr; /* rounded addr counter */ register u_int rsize; /* rounded size counter */ register u_int ssize; register addr_t addrsav; struct seg *segsav; faultcode_t res = 0; raddr = (addr_t)((u_int)addr & PAGEMASK); rsize = (((u_int)(addr + size) + PAGEOFFSET) & PAGEMASK) - (u_int)raddr; seg = as_segat(as, raddr); if (seg == NULL) return (FC_NOMAP); addrsav = raddr; segsav = seg; for (; rsize != 0; rsize -= ssize, raddr += ssize) { if (raddr >= seg->s_base + seg->s_size) { seg = seg->s_next; /* goto next seg */ if (raddr != seg->s_base) { res = FC_NOMAP; break; } } if (raddr + rsize > seg->s_base + seg->s_size) ssize = seg->s_base + seg->s_size - raddr; else ssize = rsize; res = (*seg->s_ops->fault)(seg, raddr, ssize, type, rw); if (res != 0) break; } /* * If we failed and we were locking, unlock the pages we faulted. * (Maybe we should just panic if we are SOFTLOCKing * or even SOFTUNLOCKing right here...) */ if (res != 0 && type == F_SOFTLOCK) { for (seg = segsav; addrsav < raddr; addrsav += ssize) { if (addrsav >= seg->s_base + seg->s_size) seg = seg->s_next; /* goto next seg */ /* * Now call the fault routine again to perform the * unlock using S_OTHER instead of the rw variable * since we never got a chance to touch the pages. */ if (raddr > seg->s_base + seg->s_size) ssize = seg->s_base + seg->s_size - addrsav; else ssize = raddr - addrsav; (void) (*seg->s_ops->fault)(seg, addrsav, ssize, F_SOFTUNLOCK, S_OTHER); } } return (res); } /* * Asynchronous ``fault'' at addr for size bytes. */ faultcode_t as_faulta(as, addr, size) struct as *as; addr_t addr; u_int size; { register struct seg *seg; register addr_t raddr; /* rounded addr counter */ register u_int rsize; /* rounded size counter */ faultcode_t res; raddr = (addr_t)((u_int)addr & PAGEMASK); rsize = (((u_int)(addr + size) + PAGEOFFSET) & PAGEMASK) - (u_int)raddr; seg = as_segat(as, raddr); if (seg == NULL) return (FC_NOMAP); for (; rsize != 0; rsize -= PAGESIZE, raddr += PAGESIZE) { if (raddr >= seg->s_base + seg->s_size) { seg = seg->s_next; /* goto next seg */ if (raddr != seg->s_base) return (FC_NOMAP); } res = (*seg->s_ops->faulta)(seg, raddr); if (res != 0) return (res); } return (0); } /* * Set the virtual mapping for the interval from [addr : addr + size) * in address space `as' to have the specified protection. * It is ok for the range to cross over several segments, * as long as they are contiguous. */ enum as_res as_setprot(as, addr, size, prot) struct as *as; addr_t addr; u_int size; u_int prot; { register struct seg *seg; register u_int ssize; register addr_t raddr; /* rounded addr counter */ register u_int rsize; /* rounded size counter */ enum as_res res = A_SUCCESS; raddr = (addr_t)((u_int)addr & PAGEMASK); rsize = (((u_int)(addr + size) + PAGEOFFSET) & PAGEMASK) - (u_int)raddr; seg = as_segat(as, raddr); if (seg == NULL) return (A_BADADDR); for (; rsize != 0; rsize -= ssize, raddr += ssize) { if (raddr >= seg->s_base + seg->s_size) { seg = seg->s_next; /* goto next seg */ if (raddr != seg->s_base) { res = A_BADADDR; break; } } if ((raddr + rsize) > (seg->s_base + seg->s_size)) ssize = seg->s_base + seg->s_size - raddr; else ssize = rsize; if ((*seg->s_ops->setprot)(seg, raddr, ssize, prot) != 0) res = A_OPFAIL; /* keep on going */ } return (res); } /* * Check to make sure that the interval from [addr : addr + size) * in address space `as' has at least the specified protection. * It is ok for the range to cross over several segments, as long * as they are contiguous. */ enum as_res as_checkprot(as, addr, size, prot) struct as *as; addr_t addr; u_int size; u_int prot; { register struct seg *seg; register u_int ssize; register addr_t raddr; /* rounded addr counter */ register u_int rsize; /* rounded size counter */ raddr = (addr_t)((u_int)addr & PAGEMASK); rsize = (((u_int)(addr + size) + PAGEOFFSET) & PAGEMASK) - (u_int)raddr; seg = as_segat(as, raddr); if (seg == NULL) return (A_BADADDR); for (; rsize != 0; rsize -= ssize, raddr += ssize) { if (raddr >= seg->s_base + seg->s_size) { seg = seg->s_next; /* goto next seg */ if (raddr != seg->s_base) return (A_BADADDR); } if ((raddr + rsize) > (seg->s_base + seg->s_size)) ssize = seg->s_base + seg->s_size - raddr; else ssize = rsize; if ((*seg->s_ops->checkprot)(seg, raddr, ssize, prot) != 0) return (A_OPFAIL); } return (A_SUCCESS); } enum as_res as_unmap(as, addr, size) register struct as *as; addr_t addr; u_int size; { register struct seg *seg, *seg_next; register addr_t raddr, eaddr; register u_int ssize; addr_t obase; raddr = (addr_t)((u_int)addr & PAGEMASK); eaddr = (addr_t)(((u_int)(addr + size) + PAGEOFFSET) & PAGEMASK); seg = as->a_segs; if (seg != NULL) { for (; raddr < eaddr; seg = seg_next) { /* * Save next segment pointer since seg can be * destroyed during the segment unmap operation. * We also have to save the old base below. */ seg_next = seg->s_next; if (raddr >= seg->s_base + seg->s_size) { if (seg->s_base >= seg_next->s_base) break; /* looked at all segs */ continue; /* not there yet */ } if (eaddr <= seg->s_base) break; /* all done */ if (raddr < seg->s_base) raddr = seg->s_base; /* skip to seg start */ if (eaddr > (seg->s_base + seg->s_size)) ssize = seg->s_base + seg->s_size - raddr; else ssize = eaddr - raddr; obase = seg->s_base; if ((*seg->s_ops->unmap)(seg, raddr, ssize) != 0) return (A_OPFAIL); raddr += ssize; /* * Carefully check to see if we * have looked at all the segments. */ if (as->a_segs == NULL || obase >= seg_next->s_base) break; } } return (A_SUCCESS); } int as_map(as, addr, size, crfp, argsp) struct as *as; addr_t addr; u_int size; int (*crfp)(); caddr_t argsp; { register struct seg *seg; enum as_res res; int error; seg = seg_alloc(as, addr, size); if (seg == NULL) return (ENOMEM); /* * Remember that this was the most recently touched segment. * If the create routine merges this segment into an existing * segment, seg_free will adjust the a_seglast hint. */ as->a_seglast = seg; error = (*crfp)(seg, argsp); /* * If some error occurred during the create function, destroy * this segment. Otherwise, if the address space is locked, * establish memory locks for the new segment. Translate * error returns as appropriate. */ if (error) seg_free(seg); else if (as->a_paglck) { res = as_ctl(as, seg->s_base, seg->s_size, MC_LOCK, (caddr_t)0); if (res == A_RESOURCE) error = EAGAIN; else if (res != A_SUCCESS) error = EIO; if (error) (void) as_unmap(as, addr, size); } return (error); } /* * Find a hole of at least size minlen within [base, base+len). * If flags specifies AH_HI, the hole will have the highest possible address * in the range. Otherwise, it will have the lowest possible address. * If flags specifies AH_CONTAIN, the hole will contain the address addr. * If an adequate hole is found, base and len are set to reflect the part of * the hole that is within range, and A_SUCCESS is returned. Otherwise, * A_OPFAIL is returned. * XXX This routine is not correct when base+len overflows addr_t. */ /* VARARGS5 */ enum as_res as_hole(as, minlen, basep, lenp, flags, addr) struct as *as; register u_int minlen; addr_t *basep; u_int *lenp; int flags; addr_t addr; { register addr_t lobound = *basep; register addr_t hibound = lobound + *lenp; register struct seg *sseg = as->a_segs; register struct seg *lseg, *hseg; register addr_t lo, hi; register int forward; if (sseg == NULL) if (valid_va_range(basep, lenp, minlen, flags & AH_DIR)) return (A_SUCCESS); else return (A_OPFAIL); /* * Set up to iterate over all the inter-segment holes in the given * direction. lseg is NULL for the lowest-addressed hole and hseg is * NULL for the highest-addressed hole. If moving backwards, we reset * sseg to denote the highest-addressed segment. */ forward = (flags & AH_DIR) == AH_LO; if (forward) { lseg = NULL; hseg = sseg; } else { sseg = sseg->s_prev; hseg = NULL; lseg = sseg; } for (;;) { /* * Set lo and hi to the hole's boundaries. (We should really * use MAXADDR in place of hibound in the expression below, * but can't express it easily; using hibound in its place is * harmless.) */ lo = (lseg == NULL) ? 0 : lseg->s_base + lseg->s_size; hi = (hseg == NULL) ? hibound : hseg->s_base; /* * If the iteration has moved past the interval from lobound * to hibound it's pointless to continue. */ if ((forward && lo > hibound) || (!forward && hi < lobound)) break; else if (lo > hibound || hi < lobound) goto cont; /* * Candidate hole lies at least partially within the allowable * range. Restrict it to fall completely within that range, * i.e., to [max(lo, lobound), min(hi, hibound)). */ if (lo < lobound) lo = lobound; if (hi > hibound) hi = hibound; /* * Verify that the candidate hole is big enough and meets * hardware constraints. */ *basep = lo; *lenp = hi - lo; if (valid_va_range(basep, lenp, minlen, forward ? AH_LO : AH_HI) && ((flags & AH_CONTAIN) == 0 || (*basep <= addr && *basep + *lenp > addr))) return (A_SUCCESS); cont: /* * Move to the next hole. */ if (forward) { lseg = hseg; if (lseg == NULL) break; hseg = hseg->s_next; if (hseg == sseg) hseg = NULL; } else { hseg = lseg; if (hseg == NULL) break; lseg = lseg->s_prev; if (lseg == sseg) lseg = NULL; } } return (A_OPFAIL); } /* * Return the next range within [base, base+len) that is backed * with "real memory". Skip holes and non-seg_vn segments. * We're lazy and only return one segment at a time. */ enum as_res as_memory(as, basep, lenp) struct as *as; addr_t *basep; u_int *lenp; { register struct seg *seg, *sseg, *cseg = NULL; register addr_t addr, eaddr, segend; /* XXX - really want as_segatorabove? */ if (as->a_seglast == NULL) as->a_seglast = as->a_segs; addr = *basep; eaddr = addr + *lenp; sseg = seg = as->a_seglast; if (seg != NULL) { do { if (seg->s_ops != &segvn_ops) continue; if (seg->s_base <= addr && addr < (segend = (seg->s_base + seg->s_size))) { /* found a containing segment */ as->a_seglast = seg; *basep = addr; if (segend > eaddr) *lenp = eaddr - addr; else *lenp = segend - addr; return (A_SUCCESS); } else if (seg->s_base > addr) { if (cseg == NULL || cseg->s_base > seg->s_base) /* save closest seg above */ cseg = seg; } } while ((seg = seg->s_next) != sseg); } if (cseg == NULL) /* ??? no segments in address space? */ return (A_OPFAIL); /* * Only found a close segment, see if there's * a valid range we can return. */ if (cseg->s_base > eaddr) return (A_BADADDR); /* closest segment is out of range */ as->a_seglast = cseg; *basep = cseg->s_base; if (cseg->s_base + cseg->s_size > eaddr) *lenp = eaddr - cseg->s_base; /* segment contains eaddr */ else *lenp = cseg->s_size; /* segment is between addr and eaddr */ return (A_SUCCESS); } /* * Swap the pages associated with the address space as out to * secondary storage, returning the number of bytes actually * swapped. * * If we are not doing a "hard" swap (i.e. we're just getting rid * of a deawood process) unlock the segu making it available to be * paged out. * * The value returned is intended to correlate well with the process's * memory requirements. Its usefulness for this purpose depends on * how well the segment-level routines do at returning accurate * information. */ u_int as_swapout(as, hardswap) struct as *as; short hardswap; { register struct seg *seg, *sseg; register u_int swpcnt = 0; /* * Kernel-only processes have given up their address * spaces. Of course, we shouldn't be attempting to * swap out such processes in the first place... */ if (as == NULL) return (0); /* * Free all mapping resources associated with the address * space. The segment-level swapout routines capitalize * on this unmapping by scavanging pages that have become * unmapped here. */ hat_free(as); /* * Call the swapout routines of all segments in the address * space to do the actual work, accumulating the amount of * space reclaimed. */ sseg = seg = as->a_segs; if (hardswap && seg != NULL) { do { register struct seg_ops *ov = seg->s_ops; /* for "soft" swaps, should we sync out segment instead? XXX */ if (ov->swapout != NULL) swpcnt += (*ov->swapout)(seg); } while ((seg = seg->s_next) != sseg); } return (swpcnt); } /* * Determine whether data from the mappings in interval [addr : addr + size) * are in the primary memory (core) cache. */ enum as_res as_incore(as, addr, size, vec, sizep) struct as *as; addr_t addr; u_int size; char *vec; u_int *sizep; { register struct seg *seg; register u_int ssize; register addr_t raddr; /* rounded addr counter */ register u_int rsize; /* rounded size counter */ u_int isize; /* iteration size */ *sizep = 0; raddr = (addr_t)((u_int)addr & PAGEMASK); rsize = ((((u_int)addr + size) + PAGEOFFSET) & PAGEMASK) - (u_int)raddr; seg = as_segat(as, raddr); if (seg == NULL) return (A_BADADDR); for (; rsize != 0; rsize -= ssize, raddr += ssize) { if (raddr >= seg->s_base + seg->s_size) { seg = seg->s_next; if (raddr != seg->s_base) return (A_BADADDR); } if ((raddr + rsize) > (seg->s_base + seg->s_size)) ssize = seg->s_base + seg->s_size - raddr; else ssize = rsize; *sizep += isize = (*seg->s_ops->incore)(seg, raddr, ssize, vec); if (isize != ssize) return (A_OPFAIL); vec += btoc(ssize); } return (A_SUCCESS); } /* * Cache control operations over the interval [addr : addr + size) in * address space "as". */ enum as_res as_ctl(as, addr, size, func, arg) struct as *as; addr_t addr; u_int size; int func; caddr_t arg; { register struct seg *seg; /* working segment */ register struct seg *fseg; /* first segment of address space */ register u_int ssize; /* size of seg */ register addr_t raddr; /* rounded addr counter */ register u_int rsize; /* rounded size counter */ enum as_res res; /* recursive result */ int r; /* local result */ /* * Normalize addresses and sizes. */ raddr = (addr_t)((u_int)addr & PAGEMASK); rsize = (((u_int)(addr + size) + PAGEOFFSET) & PAGEMASK) - (u_int)raddr; /* * If these are address space lock/unlock operations, loop over * all segments in the address space, as appropriate. */ if ((func == MC_LOCKAS) || (func == MC_UNLOCKAS)) { if (func == MC_UNLOCKAS) as->a_paglck = 0; else { if ((int)arg & MCL_FUTURE) as->a_paglck = 1; if (((int)arg & MCL_CURRENT) == 0) return (A_SUCCESS); } for (fseg = NULL, seg = as->a_segs; seg != fseg; seg = seg->s_next) { if (fseg == NULL) fseg = seg; if ((res = as_ctl(as, seg->s_base, seg->s_size, func == MC_LOCKAS ? MC_LOCK : MC_UNLOCK, (caddr_t)0)) != A_SUCCESS) return (res); } return (A_SUCCESS); } /* * Get initial segment. */ if ((seg = as_segat(as, raddr)) == NULL) return (A_BADADDR); /* * Loop over all segments. If a hole in the address range is * discovered, then fail. For each segment, perform the appropriate * control operation. */ while (rsize != 0) { /* * Make sure there's no hole, calculate the portion * of the next segment to be operated over. */ if (raddr >= seg->s_base + seg->s_size) { seg = seg->s_next; if (raddr != seg->s_base) return (A_BADADDR); } if ((raddr + rsize) > (seg->s_base + seg->s_size)) ssize = seg->s_base + seg->s_size - raddr; else ssize = rsize; /* * Dispatch on specific function. */ switch (func) { /* * Synchronize cached data from mappings with backing * objects. */ case MC_SYNC: if (r = (*seg->s_ops->sync) (seg, raddr, ssize, (u_int)arg)) return (r == EPERM ? A_RESOURCE : A_OPFAIL); break; /* * Lock pages in memory. */ case MC_LOCK: if (r = (*seg->s_ops->lockop)(seg, raddr, ssize, func)) return (r == EAGAIN ? A_RESOURCE : A_OPFAIL); break; /* * Unlock mapped pages. */ case MC_UNLOCK: (void) (*seg->s_ops->lockop)(seg, raddr, ssize, func); break; /* * Store VM advise for mapped pages in segment layer */ case MC_ADVISE: (void) (*seg->s_ops->advise)(seg, raddr, ssize, arg); break; /* * Can't happen. */ default: panic("as_ctl"); } rsize -= ssize; raddr += ssize; } return (A_SUCCESS); } /* * Inform the as of translation information associated with the given addr. * This is currently only called if a_hatcallback == 1. */ void as_hatsync(as, addr, ref, mod, flags) struct as *as; addr_t addr; u_int ref; u_int mod; u_int flags; { struct seg *seg; if (seg = as_segat(as, addr)) seg->s_ops->hatsync(seg, addr, ref, mod, flags); }