/*************************************************************************** * * Copyright (c) 1998, 1999 Timpanogas Research Group, Inc. * 895 West Center Street * Orem, Utah 84057 * jmerkey@timpanogas.com * * 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, version 2, or 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 are free to modify and re-distribute this program in accordance * with the terms specified in the GNU Public License. The copyright * contained in this code is required to be present in any derivative * works and you are required to provide the source code for this * program as part of any commercial or non-commercial distribution. * You are required to respect the rights of the Copyright holders * named within this code. * * jmerkey@timpanogas.com and TRG, Inc. are the official maintainers of * this code. You are encouraged to report any bugs, problems, fixes, * suggestions, and comments about this software to jmerkey@timpanogas.com * or linux-kernel@vger.rutgers.edu. New releases, patches, bug fixes, and * technical documentation can be found at www.timpanogas.com. TRG will * periodically post new releases of this software to www.timpanogas.com * that contain bug fixes and enhanced capabilities. * * Original Authorship : * source code written by Jeff V. Merkey, TRG, Inc. * * Original Contributors : * Jeff V. Merkey, TRG, Inc. * Darren Major, TRG, Inc. * Alan Cox, RedHat Software, Inc. * **************************************************************************** * * * AUTHOR : Jeff V. Merkey (jmerkey@timpanogas.com) * FILE : MMAP.C * DESCRIP : FENRIS VFS Memory Mapping Module for Linux * DATE : December 6, 1998 * * ***************************************************************************/ #include "globals.h" #if (LINUX_24) extern ULONG insert_io(ULONG disk, ASYNCH_IO *io); extern void RunAsynchIOQueue(ULONG disk); extern ULONG nwfs_to_linux_error(ULONG NwfsError); DECLARE_WAIT_QUEUE_HEAD(wait_for_page_cache_lru); kmem_cache_t *page_cache_p = 0; CACHE_AIO *page_cache_dirty_head = 0; CACHE_AIO *page_cache_dirty_tail = 0; CACHE_AIO *page_cache_lru_head = 0; CACHE_AIO *page_cache_lru_tail = 0; ULONG page_cache_lru_count = 0; ULONG page_cache_lru_waiters = 0; #if (LINUX_SLEEP) #if (LINUX_SPIN) spinlock_t page_cache_lru_spinlock = SPIN_LOCK_UNLOCKED; long page_cache_lru_flags = 0; #else NWFSInitMutex(page_cache_lru_semaphore); #endif #endif void nwfs_lock_cache(void) { #if (LINUX_SLEEP) #if (LINUX_SPIN) spin_lock_irqsave(&page_cache_lru_spinlock, page_cache_lru_flags); #else if (WaitOnSemaphore(&page_cache_lru_semaphore) == -EINTR) NWFSPrint("lock lru was interrupted\n"); #endif #endif } void nwfs_unlock_cache(void) { #if (LINUX_SLEEP) #if (LINUX_SPIN) spin_unlock_irqrestore(&page_cache_lru_spinlock, page_cache_lru_flags); #else SignalSemaphore(&page_cache_lru_semaphore); #endif #endif } void InsertDirtyCacheLRU(CACHE_AIO *cache_aio) { nwfs_lock_cache(); if (!page_cache_dirty_head) { page_cache_dirty_head = cache_aio; page_cache_dirty_tail = cache_aio; cache_aio->dnext = cache_aio->dprior = 0; } else { page_cache_dirty_tail->dnext = cache_aio; cache_aio->dnext = 0; cache_aio->dprior = page_cache_dirty_tail; page_cache_dirty_tail = cache_aio; } cache_aio->state |= L_DIRTY; nwfs_unlock_cache(); return; } CACHE_AIO *RemoveDirtyCacheLRU(CACHE_AIO *cache_aio) { nwfs_lock_cache(); if (page_cache_dirty_head == cache_aio) { page_cache_dirty_head = (void *) cache_aio->dnext; if (page_cache_dirty_head) page_cache_dirty_head->dprior = NULL; else page_cache_dirty_tail = NULL; } else { cache_aio->dprior->dnext = cache_aio->dnext; if (cache_aio != page_cache_dirty_tail) cache_aio->dnext->dprior = cache_aio->dprior; else page_cache_dirty_tail = cache_aio->dprior; } cache_aio->dnext = cache_aio->dprior = 0; cache_aio->state &= ~L_DIRTY; nwfs_unlock_cache(); return cache_aio; } void PutCacheLRU(CACHE_AIO *cache_aio) { nwfs_lock_cache(); if (!page_cache_lru_head) { page_cache_lru_head = page_cache_lru_tail = cache_aio; cache_aio->next = cache_aio->prior = 0; } else { page_cache_lru_tail->next = cache_aio; cache_aio->next = 0; cache_aio->prior = page_cache_lru_tail; page_cache_lru_tail = cache_aio; } // wake up any waiters wake_up(&wait_for_page_cache_lru); nwfs_unlock_cache(); return; } CACHE_AIO *GetFreeCacheLRU(void) { CACHE_AIO *cache_aio; nwfs_lock_cache(); if (page_cache_lru_head) { cache_aio = page_cache_lru_head; page_cache_lru_head = cache_aio->next; if (page_cache_lru_head) page_cache_lru_head->prior = NULL; else page_cache_lru_tail = NULL; nwfs_unlock_cache(); cache_aio->next = cache_aio->prior = 0; return cache_aio; } else if (page_cache_lru_count < MAX_BUFFER_HEADS) { nwfs_unlock_cache(); cache_aio = (CACHE_AIO *) kmem_cache_alloc(page_cache_p, SLAB_BUFFER); if (!cache_aio) return 0; page_cache_lru_count++; NWFSSet(cache_aio, 0, sizeof(CACHE_AIO)); return cache_aio; } nwfs_unlock_cache(); return 0; } void free_page_cache_lru_list(void) { CACHE_AIO *cache_aio; nwfs_lock_cache(); while (page_cache_lru_head) { cache_aio = page_cache_lru_head; page_cache_lru_head = cache_aio->next; if (page_cache_p) kmem_cache_free(page_cache_p, cache_aio); } page_cache_lru_head = page_cache_lru_tail = 0; if (page_cache_p) kmem_cache_shrink(page_cache_p); page_cache_p = 0; page_cache_lru_count = 0; nwfs_unlock_cache(); return; } CACHE_AIO *__GetCacheLRUWait(void) { CACHE_AIO *cache_aio; DECLARE_WAITQUEUE(wait, current); add_wait_queue_exclusive(&wait_for_page_cache_lru, &wait); for (;;) { current->state = TASK_UNINTERRUPTIBLE; cache_aio = GetFreeCacheLRU(); if (cache_aio) break; page_cache_lru_waiters++; schedule(); page_cache_lru_waiters--; } remove_wait_queue(&wait_for_page_cache_lru, &wait); current->state = TASK_RUNNING; return cache_aio; } CACHE_AIO *GetCacheLRU(void) { register CACHE_AIO *cache_aio; if (!page_cache_p) { page_cache_p = kmem_cache_create("nwfs_page_cache_lru", sizeof(CACHE_AIO), 0, SLAB_HWCACHE_ALIGN, NULL, NULL); if (!page_cache_p) { NWFSPrint("Cannot create lru SLAB cache\n"); return 0; } } cache_aio = GetFreeCacheLRU(); if (cache_aio) return cache_aio; return __GetCacheLRUWait(); } // this function returns the volume logical block number from the // logical file block number. ULONG NWFileMapBlock(VOLUME *volume, HASH *hash, ULONG Offset, ULONG *boffset) { register ULONG Length, vBlock; register long FATChain, index, voffset; register ULONG StartIndex, StartOffset, rc; register FAT_ENTRY *FAT; FAT_ENTRY FAT_S; SUBALLOC_MAP map; // if a subdirectory or bad bsize then return error if (!hash || (hash->Flags & SUBDIRECTORY_FILE)) return (ULONG) -1; if (boffset) *boffset = 0; // get the proper namespace stream cluster and size switch (hash->NameSpace) { #if (!HASH_FAT_CHAINS) register ULONG ccode; register MACINTOSH *mac; DOS dos; #endif case DOS_NAME_SPACE: #if (HASH_FAT_CHAINS) FATChain = hash->FirstBlock; Length = hash->FileSize; #else ccode = ReadDirectoryRecord(volume, &dos, hash->DirNo); if (ccode) return -1; FATChain = dos.FirstBlock; Length = dos.FileSize; #endif break; case MAC_NAME_SPACE: #if (HASH_FAT_CHAINS) FATChain = hash->FirstBlock; Length = hash->FileSize; #else ccode = ReadDirectoryRecord(volume, &dos, hash->DirNo); if (ccode) return -1; mac = (MACINTOSH *) &dos; FATChain = mac->ResourceFork; Length = mac->ResourceForkSize; #endif break; default: return (ULONG) -1; } StartIndex = Offset / volume->ClusterSize; StartOffset = Offset % volume->ClusterSize; // we always start with an index of zero index = 0; if (FATChain & 0x80000000) { // check for EOF if (FATChain == (ULONG) -1) return (ULONG) -1; // index must be null here if (StartIndex) return (ULONG) -1; rc = MapSuballocNode(volume, &map, FATChain); if (rc) return (ULONG) -1; if (StartOffset >= map.Size) return (ULONG) -1; if (StartOffset >= map.clusterSize[0]) { if (map.Count == 1) return (ULONG) -1; voffset = StartOffset - map.clusterSize[0]; vBlock = (map.clusterNumber[1] * volume->BlocksPerCluster) + ((map.clusterOffset[1] + voffset) / IO_BLOCK_SIZE); if (boffset) *boffset = ((map.clusterOffset[1] + voffset) % IO_BLOCK_SIZE); return vBlock; } else { vBlock = (map.clusterNumber[0] * volume->BlocksPerCluster) + ((map.clusterOffset[0] + StartOffset) / IO_BLOCK_SIZE); if (boffset) *boffset = ((map.clusterOffset[0] + StartOffset) % IO_BLOCK_SIZE); return vBlock; } } FAT = GetFatEntry(volume, FATChain, &FAT_S); if (FAT) index = FAT->FATIndex; while (FAT && FAT->FATCluster) { // we detected a hole in the file if (StartIndex < index) { // return 0 if we detect a hole in the file. vBlock = 0; return vBlock; } // we found our cluster in the chain if (StartIndex == index) { vBlock = (FATChain * volume->BlocksPerCluster) + (StartOffset / IO_BLOCK_SIZE); if (boffset) *boffset = (StartOffset % IO_BLOCK_SIZE); return vBlock; } // bump to the next cluster FATChain = FAT->FATCluster; // check if the next cluster is a suballoc element or EOF marker if (FATChain & 0x80000000) { // end of file if (FATChain == (ULONG) -1) return (ULONG) -1; // check for valid index if ((index + 1) == StartIndex) { rc = MapSuballocNode(volume, &map, FATChain); if (rc) return (ULONG) -1; if (StartOffset >= map.Size) return (ULONG) -1; if (StartOffset >= map.clusterSize[0]) { if (map.Count == 1) return (ULONG) -1; voffset = StartOffset - map.clusterSize[0]; vBlock = (map.clusterNumber[1] * volume->BlocksPerCluster) + ((map.clusterOffset[1] + voffset) / IO_BLOCK_SIZE); if (boffset) *boffset = ((map.clusterOffset[1] + voffset) % IO_BLOCK_SIZE); return vBlock; } else { vBlock = (map.clusterNumber[0] * volume->BlocksPerCluster) + ((map.clusterOffset[0] + StartOffset) / IO_BLOCK_SIZE); if (boffset) *boffset = ((map.clusterOffset[0] + StartOffset) % IO_BLOCK_SIZE); return vBlock; } } return (ULONG) -1; } // get next fat table entry and index FAT = GetFatEntry(volume, FATChain, &FAT_S); if (FAT) index = FAT->FATIndex; } return (ULONG) -1; } ULONG nwfs_map_extents(VOLUME *volume, HASH *hash, ULONG block, ULONG bsize, ULONG *device, ULONG extend, ULONG *retCode, ASYNCH_IO *io, struct inode *inode, ULONG *vblock, ULONG *voffset, nwvp_asynch_map *map, ULONG *count) { register ULONG ccode, disk, vBlock, vMap, mirror_index; ULONG boffset = 0; DOS dos; if (!bsize || (bsize < 512) || (bsize > 4096)) return 0; if (vblock) *vblock = 0; if (voffset) *voffset = 0; if (device) *device = 0; if (retCode) *retCode = 0; if (extend) { ccode = ReadDirectoryRecord(volume, &dos, hash->DirNo); if (ccode) return 0; // NWWriteFile() supports an extend mode that will extend the // meta-data for a file if we pass a NULL address for the data // buffer argument without writing any data to the extended // area. ccode = NWWriteFile(volume, &dos.FirstBlock, dos.Flags, block * bsize, NULL, (1 << PAGE_CACHE_SHIFT), 0, 0, retCode, KERNEL_ADDRESS_SPACE, ((volume->VolumeFlags & SUB_ALLOCATION_ON) ? 1 : 0), dos.FileAttributes); if (ccode != (1 << PAGE_CACHE_SHIFT)) return 0; if (retCode && *retCode) return 0; #if (HASH_FAT_CHAINS) hash->FirstBlock = dos.FirstBlock; #endif // since we extended the file, flag as modified to force // suballocation during file close. hash->State |= NWFS_MODIFIED; ccode = WriteDirectoryRecord(volume, &dos, hash->DirNo); if (ccode) return 0; } if (map && count) { vBlock = NWFileMapBlock(volume, hash, block * bsize, &boffset); if (!vBlock || (vBlock == (ULONG) -1)) { #if (VERBOSE) NWFSPrint("[%s] no map vBlock-%d block-%d\n", hash->Name, (int)vBlock, (int)block); #endif return 0; } ccode = nwvp_vpartition_map_asynch_write(volume->nwvp_handle, vBlock, count, map); if (!ccode && *count) { extern ULONG mirror_counter; // select a random mirror to read from mirror_index = mirror_counter % *count; mirror_counter++; disk = map[mirror_index].disk_id; if (SystemDisk[disk]) { *device = (ULONG)SystemDisk[disk]->PhysicalDiskHandle; vMap = (map[mirror_index].sector_offset / (bsize / 512)) + (boffset / bsize); if (vblock) *vblock = vBlock; if (voffset) *voffset = boffset; #if (VERBOSE) NWFSPrint("[%s] block/size-%d/%d vblock-%d vMap-%08X boff=%d ext-%d\n", hash->Name, (int)block, (int)bsize, (int)vBlock, (unsigned)vMap, (int)boffset, (int)extend); #endif return vMap; } } } return 0; } int nwfs_get_block(struct inode *inode, long block, struct buffer_head *bh_result, int create) { register VOLUME *volume = inode->i_sb->u.generic_sbp; register HASH *hash = inode->u.generic_ip; register ULONG lba, totalBlocks; ULONG device = 0, retCode = 0, count = 0; nwvp_asynch_map map[8]; #if (VERBOSE) NWFSPrint("get_block block-%d create-%d [%s]\n", (int)block, (int)create, hash->Name); #endif if (!hash) return -EBADF; if (!volume) return -EBADF; if (!inode->i_sb->s_blocksize || (inode->i_sb->s_blocksize > volume->BlockSize)) return -EBADF; totalBlocks = (volume->VolumeClusters * volume->BlocksPerCluster); totalBlocks = totalBlocks * (IO_BLOCK_SIZE / inode->i_sb->s_blocksize); if ((block < 0) || (block > totalBlocks)) { NWFSPrint("nwfs: mmap exceeded volume size block-%d total-%d\n", (int)block, (int)totalBlocks); return -EIO; } NWLockFileExclusive(hash); // if someone passes in a NULL bh_result buffer, and create is set // TRUE, then we just want to extend the file without redundantly // calling nwfs_map_extents() several times. lba = nwfs_map_extents(volume, hash, block, inode->i_sb->s_blocksize, (bh_result ? &device : NULL), (bh_result ? 0 : create), &retCode, 0, inode, 0, 0, (bh_result ? &map[0] : NULL), (bh_result ? &count : NULL)); if (retCode) { NWUnlockFile(hash); return nwfs_to_linux_error(retCode); } if (!bh_result) { NWUnlockFile(hash); return 0; } if (lba && device) { bh_result->b_dev = device; bh_result->b_blocknr = lba; bh_result->b_state |= (1UL << BH_Mapped); if (create) bh_result->b_state |= (1UL << BH_New); NWUnlockFile(hash); return 0; } if (!create) { NWUnlockFile(hash); return 0; } lba = nwfs_map_extents(volume, hash, block, inode->i_sb->s_blocksize, &device, create, &retCode, 0, inode, 0, 0, &map[0], &count); if (retCode) { NWUnlockFile(hash); return nwfs_to_linux_error(retCode); } if (lba && device) { bh_result->b_dev = device; bh_result->b_blocknr = lba; bh_result->b_state |= (1UL << BH_Mapped); bh_result->b_state |= (1UL << BH_New); NWUnlockFile(hash); return 0; } NWUnlockFile(hash); return -EIO; } ULONG release_page_cache_aio(ASYNCH_IO *io) { register CACHE_AIO *cache_aio = (CACHE_AIO *) io->call_back_parameter; register struct page *page = (struct page *)cache_aio->page; if (io->ccode) cache_aio->aio_ccode = io->ccode; cache_aio->mirrors_completed++; if (cache_aio->mirror_count == cache_aio->mirrors_completed) { if (cache_aio->aio_ccode) NWFSPrint("I/O error in callback\n"); PutCacheLRU(cache_aio); page->buffers = NULL; SetPageUptodate(page); } return 0; } int nwfs_prepare_write(struct file *file, struct page *page, unsigned from, unsigned to) { return (block_prepare_write(page, from, to, nwfs_get_block)); } int nwfs_readpage(struct file *file, struct page *page) { return (block_read_full_page(page, nwfs_get_block)); } int nwfs_bmap(struct address_space *mapping, long block) { return (generic_block_bmap(mapping, block, nwfs_get_block)); } int nwfs_writepage(struct file *file, struct page *page) { return (block_write_full_page(page, nwfs_get_block)); } int nwfs_commit_write(struct file *file, struct page *page, unsigned from, unsigned to) { return (generic_commit_write(file, page, from, to)); } struct address_space_operations nwfs_aops = { readpage: nwfs_readpage, writepage: nwfs_writepage, prepare_write: nwfs_prepare_write, commit_write: nwfs_commit_write, bmap: nwfs_bmap, }; #endif