/* Pager for ext2fs Copyright (C) 1994, 1995 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include "ext2fs.h" spin_lock_t pagerlistlock = SPIN_LOCK_INITIALIZER; struct user_pager_info *filepagerlist; spin_lock_t node2pagelock = SPIN_LOCK_INITIALIZER; #ifdef DONT_CACHE_MEMORY_OBJECTS #define MAY_CACHE 0 #else #define MAY_CACHE 1 #endif /* Find the location on disk of page OFFSET in pager UPI. Return the disk address (in disk block) in *ADDR. If *NPLOCK is set on return, then release that mutex after I/O on the data has completed. Set DISKSIZE to be the amount of valid data on disk. (If this is an unallocated block, then set *ADDR to zero.) */ static error_t find_address (struct user_pager_info *upi, vm_address_t offset, daddr_t *addr, int *disksize, struct rwlock **nplock) { assert (upi->type == DISK || upi->type == FILE_DATA); if (upi->type == DISK) { *disksize = vm_page_size; *addr = offset / DEV_BSIZE; *nplock = 0; return 0; } else { struct node *np = upi->np; rwlock_reader_lock (&np->dn->allocptrlock); *nplock = &np->dn->allocptrlock; if (offset >= np->allocsize) { rwlock_reader_unlock (&np->dn->allocptrlock); return EIO; } if (offset + vm_page_size > np->allocsize) *disksize = np->allocsize - offset; else *disksize = vm_page_size; return ext2_getblk(np, offset / block_size, 0, addr); } } /* Implement the pager_read_page callback from the pager library. See for the interface description. */ error_t pager_read_page (struct user_pager_info *pager, vm_offset_t page, vm_address_t *buf, int *writelock) { error_t err; struct rwlock *nplock; daddr_t addr; int disksize; err = find_address (pager, page, &addr, &disksize, &nplock); if (err) return err; if (addr) { err = dev_read_sync (addr, (void *)buf, disksize); if (!err && disksize != vm_page_size) bzero ((void *)(*buf + disksize), vm_page_size - disksize); *writelock = 0; } else { vm_allocate (mach_task_self (), buf, vm_page_size, 1); *writelock = 1; } if (nplock) rwlock_reader_unlock (nplock); return err; } /* Implement the pager_write_page callback from the pager library. See for the interface description. */ error_t pager_write_page (struct user_pager_info *pager, vm_offset_t page, vm_address_t buf) { daddr_t addr; int disksize; struct rwlock *nplock; error_t err; err = find_address (pager, page, &addr, &disksize, &nplock); if (err) return err; if (addr) err = dev_write_sync (addr, buf, disksize); else { ext2_error("pager_write_page", "Attempt to write unallocated disk;" " object = %p; offset = 0x%x", pager, page); /* unallocated disk; error would be pointless */ err = 0; } if (nplock) rwlock_reader_unlock (nplock); return err; } /* Implement the pager_unlock_page callback from the pager library. See for the interface description. */ error_t pager_unlock_page (struct user_pager_info *pager, vm_offset_t address) { if (pager->type == DISK) return 0; else { error_t err; struct node *np = pager->np; struct disknode *dn = np->dn; int block_size_shift = EXT2_BLOCK_SIZE_BITS(sblock); rwlock_writer_lock (&dn->allocptrlock); /* If this is the last block, we don't let it get unlocked. */ if (address + vm_page_size > ((np->allocsize >> block_size_shift) << block_size_shift)) { ext2_error ("pager_unlock_page", "attempt to unlock at last block denied\n"); rwlock_writer_unlock (&dn->allocptrlock); return EIO; } err = diskfs_catch_exception (); if (!err) err = ext2_getblk(np, address / block_size, 1, &buf); diskfs_end_catch_exception (); rwlock_writer_unlock (&dn->allocptrlock); return err; } } /* Implement the pager_report_extent callback from the pager library. See for the interface description. */ inline error_t pager_report_extent (struct user_pager_info *pager, vm_address_t *offset, vm_size_t *size) { assert (pager->type == DISK || pager->type == FILE_DATA); *offset = 0; if (pager->type == DISK) *size = diskpagersize; else *size = pager->np->allocsize; return 0; } /* Implement the pager_clear_user_data callback from the pager library. See for the interface description. */ void pager_clear_user_data (struct user_pager_info *upi) { assert (upi->type == FILE_DATA); spin_lock (&node2pagelock); upi->np->dn->fileinfo = 0; spin_unlock (&node2pagelock); diskfs_nrele_light (upi->np); *upi->prevp = upi->next; if (upi->next) upi->next->prevp = upi->prevp; free (upi); } /* Create a the DISK pager, initializing DISKPAGER, and DISKPAGERPORT */ void create_disk_pager () { diskpager = malloc (sizeof (struct user_pager_info)); diskpager->type = DISK; diskpager->np = 0; diskpager->p = pager_create (diskpager, MAY_CACHE, MEMORY_OBJECT_COPY_NONE); diskpagerport = pager_get_port (diskpager->p); mach_port_insert_right (mach_task_self (), diskpagerport, diskpagerport, MACH_MSG_TYPE_MAKE_SEND); } /* This syncs a single file (NP) to disk. Wait for all I/O to complete if WAIT is set. NP->lock must be held. */ void diskfs_file_update (struct node *np, int wait) { struct dirty_indir *d, *tmp; struct user_pager_info *upi; spin_lock (&node2pagelock); upi = np->dn->fileinfo; if (upi) pager_reference (upi->p); spin_unlock (&node2pagelock); if (upi) { pager_sync (upi->p, wait); pager_unreference (upi->p); } for (d = np->dn->dirty; d; d = tmp) { sync_disk_image (d->bno, block_size, wait); tmp = d->next; free (d); } np->dn->dirty = 0; diskfs_node_update (np, wait); } /* Call this to create a FILE_DATA pager and return a send right. NP must be locked. */ mach_port_t diskfs_get_filemap (struct node *np) { struct user_pager_info *upi; mach_port_t right; assert (S_ISDIR (np->dn_stat.st_mode) || S_ISREG (np->dn_stat.st_mode) || (S_ISLNK (np->dn_stat.st_mode) && (!direct_symlink_extension || np->dn_stat.st_size >= sblock->fs_maxsymlinklen))); spin_lock (&node2pagelock); if (!np->dn->fileinfo) { upi = malloc (sizeof (struct user_pager_info)); upi->type = FILE_DATA; upi->np = np; diskfs_nref_light (np); upi->p = pager_create (upi, MAY_CACHE, MEMORY_OBJECT_COPY_DELAY); np->dn->fileinfo = upi; spin_lock (&pagerlistlock); upi->next = filepagerlist; upi->prevp = &filepagerlist; if (upi->next) upi->next->prevp = &upi->next; filepagerlist = upi; spin_unlock (&pagerlistlock); } right = pager_get_port (np->dn->fileinfo->p); spin_unlock (&node2pagelock); mach_port_insert_right (mach_task_self (), right, right, MACH_MSG_TYPE_MAKE_SEND); return right; } /* Call this when we should turn off caching so that unused memory object ports get freed. */ void drop_pager_softrefs (struct node *np) { struct user_pager_info *upi; spin_lock (&node2pagelock); upi = np->dn->fileinfo; if (upi) pager_reference (upi->p); spin_unlock (&node2pagelock); if (MAY_CACHE && upi) pager_change_attributes (upi->p, 0, MEMORY_OBJECT_COPY_DELAY, 0); if (upi) pager_unreference (upi->p); } /* Call this when we should turn on caching because it's no longer important for unused memory object ports to get freed. */ void allow_pager_softrefs (struct node *np) { struct user_pager_info *upi; spin_lock (&node2pagelock); upi = np->dn->fileinfo; if (upi) pager_reference (upi->p); spin_unlock (&node2pagelock); if (MAY_CACHE && upi) pager_change_attributes (upi->p, 1, MEMORY_OBJECT_COPY_DELAY, 0); if (upi) pager_unreference (upi->p); } /* Call this to find out the struct pager * corresponding to the FILE_DATA pager of inode IP. This should be used *only* as a subsequent argument to register_memory_fault_area, and will be deleted when the kernel interface is fixed. NP must be locked. */ struct pager * diskfs_get_filemap_pager_struct (struct node *np) { /* This is safe because fileinfo can't be cleared; there must be an active mapping for this to be called. */ return np->dn->fileinfo->p; } /* Call function FUNC (which takes one argument, a pager) on each pager, with all file pagers being processed before the disk pager. Make the calls while holding no locks. */ static void pager_traverse (void (*func)(struct user_pager_info *)) { struct user_pager_info *p; struct item {struct item *next; struct user_pager_info *p;} *list = 0; struct item *i; spin_lock (&pagerlistlock); for (p = filepagerlist; p; p = p->next) { i = alloca (sizeof (struct item)); i->next = list; list = i; pager_reference (p->p); i->p = p; } spin_unlock (&pagerlistlock); for (i = list; i; i = i->next) { (*func)(i->p); pager_unreference (i->p->p); } (*func)(diskpager); } /* Shutdown all the pagers. */ void diskfs_shutdown_pager () { void shutdown_one (struct user_pager_info *p) { pager_shutdown (p->p); } copy_sblock (); write_all_disknodes (); pager_traverse (shutdown_one); } /* Sync all the pagers. */ void diskfs_sync_everything (int wait) { void sync_one (struct user_pager_info *p) { if (p != diskpager) pager_sync (p->p, wait); else sync_disk (wait); } copy_sblock (); write_all_disknodes (); pager_traverse (sync_one); }