/* Copyright (C) 2005,2006 David Decotigny 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 of the License, 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "blkdev.h" struct sos_blockdev_instance { /** * Unique identifier (blockdev-wide) of this block device used by * the sync_all_blockdev function. This enables sync_all_blockdev to * be resilient to other register_partition/disk calls */ sos_ui64_t uid; /** * Size of a block in bytes. */ sos_size_t block_size; /** * Size of the device, in blocks */ sos_lcount_t number_of_blocks; /** * For a partition: reference to the master disk and index of the * first block in it */ struct sos_blockdev_instance *parent_blockdev; sos_luoffset_t index_of_first_block; /** Major/minor for the device */ struct sos_fs_dev_id_t dev_id; struct sos_blockdev_operations * operations; /** * Cache of blocks for this device * @note Shared between a disk and all its (sub-(sub-(...)))partitions */ struct sos_block_cache * blk_cache; /** * Cache of mapped pages for this device * * @note Proper to each disk and every * (sub-(sub-(...)))partitions. It cannot be shared because the * partitions on a disk may not start on a page boundary... but on a * sector boundary. */ struct sos_fs_pagecache * map_cache; /* Yes, a block device can be mapped ! */ struct sos_umem_vmm_mapped_resource mapres; void * custom_data; sos_count_t ref_cnt; struct sos_blockdev_instance *next, *prev; }; /** The list of all block devices registered */ static struct sos_blockdev_instance *registered_blockdev_instances; /** Last UID delivered for the FS instances */ static sos_ui64_t last_fs_instance_uid; /* Forward declarations */ static sos_ret_t blockdev_sync_dirty_page(sos_luoffset_t offset, sos_vaddr_t dirty_page, void * custom_data); static sos_ret_t blockdev_helper_new_opened_file(struct sos_fs_node * this, const struct sos_process * owner, sos_ui32_t open_flags, struct sos_fs_opened_file ** result_of); static sos_ret_t blockdev_helper_close_opened_file(struct sos_fs_node * this, struct sos_fs_opened_file * of); static sos_ret_t duplicate_opened_blockdev(struct sos_fs_opened_file *this, const struct sos_process * for_owner, struct sos_fs_opened_file **result_of); static sos_ret_t blockdev_new_mapping(struct sos_umem_vmm_vr *); static struct sos_fs_ops_opened_file blockdev_ops_opened_file; static struct sos_fs_ops_opened_blockdev blockdev_ops_opened_blockdev; /** The thread routine called to flush the cache contents to disk */ static void bdflush_thread(void * unused) __attribute__((noreturn)); static void bdflush_thread(void * unused) { while (1) { struct sos_time t = (struct sos_time) { .sec=30, .nanosec=0 }; sos_thread_sleep(&t); sos_blockdev_sync_all_devices(); } } sos_ret_t sos_blockdev_subsystem_setup() { sos_ret_t retval; last_fs_instance_uid = 42; retval = sos_blkcache_subsystem_setup(); if (SOS_OK != retval) return retval; /* Create the bdflush kernel thread */ if (NULL == sos_create_kernel_thread("bdflush", bdflush_thread, NULL, SOS_SCHED_PRIO_TS_LOWEST)) return -SOS_ENOMEM; return SOS_OK; } /** Helper function used to increment the reference count for the device */ static sos_ret_t blockdev_use_instance(struct sos_blockdev_instance * blockdev) { SOS_ASSERT_FATAL(blockdev != NULL); SOS_ASSERT_FATAL(blockdev->ref_cnt > 0); blockdev->ref_cnt ++; return SOS_OK; } /** Helper function used to decrement the reference count for the device */ sos_ret_t sos_blockdev_release_instance(struct sos_blockdev_instance * blockdev) { SOS_ASSERT_FATAL(blockdev->ref_cnt > 1); blockdev->ref_cnt --; return SOS_OK; } /** * Return the device instance structure for the corresponding device * class/instance, or NULL when none found. */ static struct sos_blockdev_instance* lookup_blockdev_instance(sos_ui32_t device_class, sos_ui32_t device_instance) { struct sos_blockdev_instance * blockdev; int nb; list_foreach (registered_blockdev_instances, blockdev, nb) { if (blockdev->dev_id.device_class != device_class) continue; if (blockdev->dev_id.device_instance != device_instance) continue; return blockdev; } return NULL; } sos_ret_t sos_blockdev_register_disk (sos_ui32_t device_class, sos_ui32_t device_instance, sos_size_t block_size, sos_lcount_t number_of_blocks, sos_count_t blkcache_size_in_blocks, struct sos_blockdev_operations * blockdev_ops, void * blockdev_instance_custom_data) { struct sos_blockdev_instance * blockdev; blockdev = lookup_blockdev_instance(device_class, device_instance); if (NULL != blockdev) return -SOS_EBUSY; if (block_size <= 0) return -SOS_EINVAL; if (number_of_blocks <= 0) return -SOS_EINVAL; blockdev = (struct sos_blockdev_instance*) sos_kmalloc(sizeof(struct sos_blockdev_instance), 0); if (NULL == blockdev) return -SOS_ENOMEM; /* Create a new blk_cache */ blockdev->blk_cache = sos_blkcache_new_cache(blockdev_instance_custom_data, block_size, blkcache_size_in_blocks, blockdev_ops); if (NULL == blockdev->blk_cache) { sos_kfree((sos_vaddr_t) blockdev); return -SOS_ENOMEM; } /* Create a new page cache for pages mapped */ blockdev->map_cache = sos_fs_pagecache_new_cache((sos_fs_pagecache_sync_function_t) blockdev_sync_dirty_page, (void*)blockdev); if (NULL == blockdev->map_cache) { sos_blkcache_delete_cache(blockdev->blk_cache); sos_kfree((sos_vaddr_t) blockdev); return -SOS_ENOMEM; } /* Description of the device */ blockdev->dev_id.device_class = device_class; blockdev->dev_id.device_instance = device_instance; /* Description of the storage */ blockdev->block_size = block_size; blockdev->number_of_blocks = number_of_blocks; blockdev->parent_blockdev = NULL; blockdev->index_of_first_block = 0; /* Prepare the blkcache related stuff */ blockdev->operations = blockdev_ops; blockdev->custom_data = blockdev_instance_custom_data; /* Prepare the mmap related stuff */ blockdev->mapres.allowed_access_rights = SOS_VM_MAP_PROT_READ | SOS_VM_MAP_PROT_WRITE | SOS_VM_MAP_PROT_EXEC; blockdev->mapres.flags = 0; list_init(blockdev->mapres.list_vr); blockdev->mapres.custom_data = (void*)blockdev; blockdev->mapres.mmap = blockdev_new_mapping; blockdev->ref_cnt = 1; blockdev->uid = last_fs_instance_uid ++; list_add_tail(registered_blockdev_instances, blockdev); return SOS_OK; } sos_ret_t sos_blockdev_register_partition(sos_ui32_t device_class, sos_ui32_t device_instance, struct sos_blockdev_instance * parent_bd, sos_luoffset_t index_of_first_block, sos_lcount_t number_of_blocks, void * blockdev_instance_custom_data) { struct sos_blockdev_instance * blockdev; if (NULL == parent_bd) return -SOS_EINVAL; /* Make sure this partitions fits in parent partition or disk */ if (index_of_first_block + number_of_blocks > parent_bd->number_of_blocks) return -SOS_EINVAL; blockdev = lookup_blockdev_instance(device_class, device_instance); if (NULL != blockdev) return -SOS_EBUSY; blockdev = (struct sos_blockdev_instance*) sos_kmalloc(sizeof(struct sos_blockdev_instance), 0); if (NULL == blockdev) return -SOS_ENOMEM; /* Create the page cache for the partition */ blockdev->map_cache = sos_fs_pagecache_new_cache((sos_fs_pagecache_sync_function_t) blockdev_sync_dirty_page, (void*)blockdev); if (NULL == blockdev->map_cache) { sos_kfree((sos_vaddr_t) blockdev); return -SOS_ENOMEM; } /* Increase parent's reference count */ blockdev_use_instance(parent_bd); /* Description of the device */ blockdev->dev_id.device_class = device_class; blockdev->dev_id.device_instance = device_instance; /* Description of the storage */ blockdev->block_size = parent_bd->block_size; blockdev->number_of_blocks = number_of_blocks; blockdev->parent_blockdev = parent_bd; blockdev->index_of_first_block = parent_bd->index_of_first_block + index_of_first_block; /* Prepare the blkcache related stuff */ blockdev->operations = parent_bd->operations; blockdev->blk_cache = parent_bd->blk_cache; blockdev->custom_data = blockdev_instance_custom_data; /* Prepare the mmap related stuff */ blockdev->mapres.allowed_access_rights = SOS_VM_MAP_PROT_READ | SOS_VM_MAP_PROT_WRITE | SOS_VM_MAP_PROT_EXEC; blockdev->mapres.flags = 0; list_init(blockdev->mapres.list_vr); blockdev->mapres.custom_data = (void*)blockdev; blockdev->mapres.mmap = blockdev_new_mapping; blockdev->ref_cnt = 1; blockdev->uid = last_fs_instance_uid ++; list_add_tail(registered_blockdev_instances, blockdev); return SOS_OK; } sos_ret_t sos_blockdev_unregister_device (sos_ui32_t device_class, sos_ui32_t device_instance) { struct sos_blockdev_instance * blockdev; blockdev = lookup_blockdev_instance(device_class, device_instance); if (NULL == blockdev) return -SOS_ENODEV; if (blockdev->ref_cnt != 1) return -SOS_EBUSY; /* Unallocate the page cache */ sos_fs_pagecache_delete_cache(blockdev->map_cache); /* Unreference parent block device, if any */ if (NULL != blockdev->parent_blockdev) { blockdev->parent_blockdev->ref_cnt --; } else { /* Otherwise: we are the parent BD => unallocate the block cache */ sos_blkcache_delete_cache(blockdev->blk_cache); } list_delete(registered_blockdev_instances, blockdev); return sos_kfree((sos_vaddr_t)blockdev); } /** Generic read function working both with kernel and userspace addresses. The pagecache is tried first, and the block cache if the page cache does not have the data yet */ static sos_ret_t blockdev_generic_read(struct sos_blockdev_instance * blockdev, sos_luoffset_t offset_in_device, sos_genaddr_t buff_addr, sos_size_t * /* in/out */len, sos_bool_t bypass_pagecache) { sos_size_t rdbytes = 0; while (rdbytes < *len) { sos_ret_t retval; sos_size_t offset_in_block, wrbytes; /* Get the block at the current offset */ sos_luoffset_t block_id = offset_in_device / blockdev->block_size; SOS_GENADDR_DECL(gaddr, buff_addr.is_user, buff_addr.addr + rdbytes); /* reaching the end of the device ? */ if (block_id >= blockdev->number_of_blocks) break; wrbytes = *len - rdbytes; /* Trying to get (part of) the remaining from page cache */ if (! bypass_pagecache) retval = sos_fs_pagecache_read(blockdev->map_cache, offset_in_device, gaddr, & wrbytes); else retval = -SOS_ENOENT; if (-SOS_ENOENT != retval) { /* (partial) success ! */ rdbytes += wrbytes; offset_in_device += wrbytes; if (SOS_OK == retval) continue; /* Complete block read */ else break; /* Partial read */ } /* Translating this block index into an offset inside the disk */ block_id += blockdev->index_of_first_block; /* Retrieve the block from block cache */ sos_vaddr_t block_data; struct sos_block_cache_entry * bkcache_entry = sos_blkcache_retrieve_block(blockdev->blk_cache, block_id, SOS_BLKCACHE_READ_ONLY, & block_data); if (NULL == bkcache_entry) break; /* Copy the data to user */ offset_in_block = offset_in_device % blockdev->block_size; wrbytes = blockdev->block_size - offset_in_block; if (*len - rdbytes < wrbytes) wrbytes = *len - rdbytes; retval = sos_memcpy_generic_to(gaddr, block_data + offset_in_block, wrbytes); /* Release this block back to the cache */ sos_blkcache_release_block(blockdev->blk_cache, bkcache_entry, FALSE, FALSE); if (retval > 0) { rdbytes += retval; offset_in_device += retval; } if (retval != (sos_ret_t)wrbytes) break; /* Partial read */ } *len = rdbytes; return SOS_OK; } /** Generic write function working both with kernel and userspace addresses. The pagecache is tried first, and the block cache if the page cache does not have the data yet */ static sos_ret_t blockdev_generic_write(struct sos_blockdev_instance * blockdev, sos_luoffset_t offset_in_device, sos_genaddr_t buff_addr, sos_size_t * /* in/out */len, sos_bool_t synchronous_write, sos_bool_t bypass_pagecache) { sos_size_t wrbytes = 0; while (wrbytes < *len) { sos_ret_t retval; sos_size_t offset_in_block, usrbytes; sos_blkcache_access_type_t access_type; /* Get the block at the current file offset */ sos_luoffset_t block_id = offset_in_device / blockdev->block_size; SOS_GENADDR_DECL(gaddr, buff_addr.is_user, buff_addr.addr + wrbytes); /* reaching the end of the device ? */ if (block_id >= blockdev->number_of_blocks) break; usrbytes = *len - wrbytes; /* Trying to write (part of) the remaining into page cache */ if (! bypass_pagecache) retval = sos_fs_pagecache_write(blockdev->map_cache, offset_in_device, gaddr, & usrbytes, synchronous_write); else retval = -SOS_ENOENT; if (-SOS_ENOENT != retval) { /* Success ! */ wrbytes += usrbytes; offset_in_device += usrbytes; if (SOS_OK == retval) continue; else break; /* Partial write */ } /* Translating this block index into an offset inside the disk */ block_id += blockdev->index_of_first_block; /* Compute size of data to copy */ offset_in_block = offset_in_device % blockdev->block_size; usrbytes = blockdev->block_size - offset_in_block; if (*len - wrbytes < usrbytes) usrbytes = *len - wrbytes; if (usrbytes != blockdev->block_size) /* Write partial block contents */ access_type = SOS_BLKCACHE_READ_WRITE; else /* Overwrite full block ! */ access_type = SOS_BLKCACHE_WRITE_ONLY; /* Retrieve the block from block cache */ sos_vaddr_t block_data; struct sos_block_cache_entry * bkcache_entry = sos_blkcache_retrieve_block(blockdev->blk_cache, block_id, access_type, & block_data); if (NULL == bkcache_entry) break; /* Copy the data to user */ retval = sos_memcpy_generic_from(block_data + offset_in_block, gaddr, usrbytes); /* Release this block back to the blk_cache and/or to disk */ sos_blkcache_release_block(blockdev->blk_cache, bkcache_entry, TRUE, synchronous_write); if (retval > 0) { wrbytes += retval; offset_in_device += retval; } if (retval != (sos_ret_t)usrbytes) break; /* Partial write */ } *len = wrbytes; return SOS_OK; } struct sos_blockdev_instance * sos_blockdev_ref_instance(sos_ui32_t device_class, sos_ui32_t device_instance) { struct sos_blockdev_instance * blockdev; blockdev = lookup_blockdev_instance(device_class, device_instance); if (NULL == blockdev) return NULL; blockdev_use_instance(blockdev); return blockdev; } sos_ret_t sos_blockdev_kernel_read(struct sos_blockdev_instance * blockdev, sos_luoffset_t offset, sos_vaddr_t dest_buf, sos_size_t * /* in/out */len) { sos_ret_t retval; SOS_GENADDR_DECL(gaddr, FALSE, dest_buf); blockdev_use_instance(blockdev); retval = blockdev_generic_read(blockdev, offset, gaddr, len, FALSE); sos_blockdev_release_instance(blockdev); return retval; } sos_ret_t sos_blockdev_kernel_write(struct sos_blockdev_instance * blockdev, sos_luoffset_t offset, sos_vaddr_t src_buf, sos_size_t * /* in/out */len) { sos_ret_t retval; SOS_GENADDR_DECL(gaddr, FALSE, src_buf); blockdev_use_instance(blockdev); retval = blockdev_generic_write(blockdev, offset, gaddr, len, FALSE, FALSE); sos_blockdev_release_instance(blockdev); return retval; } /** Callback called by the pagecache to transfer a mapped page back to disk */ static sos_ret_t blockdev_sync_dirty_page(sos_luoffset_t offset, sos_vaddr_t dirty_page, void * custom_data) { sos_ret_t retval; struct sos_blockdev_instance * blockdev = (struct sos_blockdev_instance*) custom_data; sos_size_t len = SOS_PAGE_SIZE; SOS_GENADDR_DECL(gaddr, FALSE, dirty_page); blockdev_use_instance(blockdev); retval = blockdev_generic_write(blockdev, offset, gaddr, &len, FALSE, TRUE); sos_blockdev_release_instance(blockdev); if (SOS_OK != retval) return retval; if (SOS_PAGE_SIZE != len) return -SOS_EIO; return SOS_OK; } sos_ret_t sos_blockdev_sync(struct sos_blockdev_instance * blockdev) { sos_ret_t retval_bc, retval_pc; blockdev_use_instance(blockdev); retval_pc = sos_fs_pagecache_sync(blockdev->map_cache); retval_bc = sos_blkcache_flush(blockdev->blk_cache); sos_blockdev_release_instance(blockdev); if (SOS_OK == retval_bc) return retval_pc; return retval_bc; } sos_ret_t sos_blockdev_sync_all_devices() { int dummy = 0; sos_ui64_t uid = 0; /* This scan will be exhaustive and resilient to addition/removal of devices as long as new devices are added with list_add_tail (because the scan is "forward", ie in order head -> tail) */ while (1) { /* Iterate over the block devices */ struct sos_blockdev_instance *blockdev; int nbd; /* As long as we don't block, we can safely access the prev/next fields of the device instance */ list_foreach_forward(registered_blockdev_instances, blockdev, nbd) { if (blockdev->uid <= uid) continue; uid = blockdev->uid; sos_blockdev_sync(blockdev); /* We must NOT continue the loops because the prev/next/current fs types/instances might have been removed or added (sync blocks, by definition) ! */ goto lookup_next_bd; } /* Reached the end of the list */ break; lookup_next_bd: /* Loop over */ dummy ++; } return SOS_OK; } /* *************************** * Callbacks needed by the VFS */ sos_ret_t sos_blockdev_helper_ref_new_fsnode(struct sos_fs_node * this) { struct sos_blockdev_instance * blockdev; blockdev = sos_blockdev_ref_instance(this->dev_id.device_class, this->dev_id.device_instance); this->block_device = blockdev; this->new_opened_file = blockdev_helper_new_opened_file; this->close_opened_file = blockdev_helper_close_opened_file; return SOS_OK; } sos_ret_t sos_blockdev_helper_release_fsnode(struct sos_fs_node * this) { if (NULL != this->block_device) return sos_blockdev_release_instance(this->block_device); return SOS_OK; } sos_ret_t sos_blockdev_helper_sync_fsnode(struct sos_fs_node * this) { if (NULL != this->block_device) return sos_blockdev_sync(this->block_device); return SOS_OK; } static sos_ret_t blockdev_helper_new_opened_file(struct sos_fs_node * this, const struct sos_process * owner, sos_ui32_t open_flags, struct sos_fs_opened_file ** result_of) { /* Lookup the character device description structure */ struct sos_blockdev_instance * blockdev = this->block_device; if (NULL == blockdev) return -SOS_ENODEV; /* Allocate the new "open file" description structure */ *result_of = (struct sos_fs_opened_file*) sos_kmalloc(sizeof(struct sos_fs_opened_file), 0); if (NULL == *result_of) return -SOS_ENOMEM; memset(*result_of, 0x0, sizeof(struct sos_fs_opened_file)); /* Initialize the read/write/seek callbacks */ (*result_of)->owner = owner; (*result_of)->open_flags = open_flags; (*result_of)->ops_file = & blockdev_ops_opened_file; (*result_of)->ops_blockdev = & blockdev_ops_opened_blockdev; /* Specify the duplicate method */ (*result_of)->duplicate = duplicate_opened_blockdev; return SOS_OK; } static sos_ret_t blockdev_helper_close_opened_file(struct sos_fs_node * this, struct sos_fs_opened_file * of) { return sos_kfree((sos_vaddr_t) of); } static sos_ret_t duplicate_opened_blockdev(struct sos_fs_opened_file *this, const struct sos_process * for_owner, struct sos_fs_opened_file **result_of) { /* Allocate the new "open file" description structure */ *result_of = (struct sos_fs_opened_file*) sos_kmalloc(sizeof(struct sos_fs_opened_file), 0); if (NULL == *result_of) return -SOS_ENOMEM; memcpy(*result_of, this, sizeof(struct sos_fs_opened_file)); (*result_of)->owner = for_owner; (*result_of)->direntry = NULL; return SOS_OK; } void * sos_blockdev_get_instance_custom_data(struct sos_blockdev_instance * blockdev) { return blockdev->custom_data; } /* * FS generic block device wrapper functions */ static sos_ret_t blockdev_wrap_seek(struct sos_fs_opened_file *this, sos_lsoffset_t offset, sos_seek_whence_t whence, /* out */ sos_lsoffset_t * result_position) { /* Make sure the device is supported by this driver */ struct sos_fs_node * fsnode = sos_fs_nscache_get_fs_node(this->direntry); /* Artificiallly update the position in the "file" */ sos_lsoffset_t ref_offs; sos_lsoffset_t dev_size = fsnode->block_device->block_size * fsnode->block_device->number_of_blocks; *result_position = this->position; switch (whence) { case SOS_SEEK_SET: ref_offs = 0; break; case SOS_SEEK_CUR: ref_offs = this->position; break; case SOS_SEEK_END: ref_offs = dev_size; break; default: return -SOS_EINVAL; } /* Forbid accesses "before" the start of the device */ if (offset < -ref_offs) return -SOS_EINVAL; /* Forbid accesses "after" the end of the device */ else if (ref_offs + offset > dev_size) return -SOS_EINVAL; this->position = ref_offs + offset; *result_position = this->position; return SOS_OK; } static sos_ret_t blockdev_wrap_read(struct sos_fs_opened_file *this, sos_uaddr_t dest_buf, sos_size_t * /* in/out */len) { struct sos_fs_node * fsnode = sos_fs_nscache_get_fs_node(this->direntry); struct sos_blockdev_instance * blockdev = fsnode->block_device; SOS_GENADDR_DECL(gaddr, TRUE, dest_buf); sos_ret_t retval = blockdev_generic_read(blockdev, this->position, gaddr, len, FALSE); this->position += *len; return retval; } static sos_ret_t blockdev_wrap_write(struct sos_fs_opened_file *this, sos_uaddr_t src_buf, sos_size_t * /* in/out */len) { struct sos_fs_node * fsnode = sos_fs_nscache_get_fs_node(this->direntry); struct sos_blockdev_instance * blockdev = fsnode->block_device; SOS_GENADDR_DECL(gaddr, TRUE, src_buf); sos_ret_t retval = blockdev_generic_write(blockdev, this->position, gaddr, len, this->open_flags & SOS_FS_OPEN_SYNC, FALSE); this->position += *len; return retval; } static sos_ret_t blockdev_wrap_mmap(struct sos_fs_opened_file *this, sos_uaddr_t *uaddr, sos_size_t size, sos_ui32_t access_rights, sos_ui32_t flags, sos_luoffset_t offset) { struct sos_fs_node * fsnode = sos_fs_nscache_get_fs_node(this->direntry); struct sos_blockdev_instance * blockdev = fsnode->block_device; if (! SOS_IS_PAGE_ALIGNED(offset)) return -SOS_EINVAL; return sos_umem_vmm_map(sos_process_get_address_space(this->owner), uaddr, size, access_rights, flags, & blockdev->mapres, offset); } static sos_ret_t blockdev_wrap_fcntl(struct sos_fs_opened_file *this, int req_id, sos_ui32_t req_arg) { return -SOS_ENOSYS; } static sos_ret_t blockdev_wrap_ioctl(struct sos_fs_opened_file *this, int req_id, sos_ui32_t req_arg) { struct sos_fs_node * fsnode = sos_fs_nscache_get_fs_node(this->direntry); struct sos_blockdev_instance * blockdev = fsnode->block_device; if (req_id == SOS_IOCTL_BLOCKDEV_SYNC) return sos_blockdev_sync(blockdev); if (blockdev->operations->ioctl) return blockdev->operations->ioctl(this, req_id, req_arg); return -SOS_ENOSYS; } static struct sos_fs_ops_opened_file blockdev_ops_opened_file = (struct sos_fs_ops_opened_file) { .seek = blockdev_wrap_seek, .read = blockdev_wrap_read, .write = blockdev_wrap_write, .mmap = blockdev_wrap_mmap, .fcntl = blockdev_wrap_fcntl }; static struct sos_fs_ops_opened_blockdev blockdev_ops_opened_blockdev = (struct sos_fs_ops_opened_blockdev) { .ioctl = blockdev_wrap_ioctl }; /* **************************** * Callbacks needed by umem_vmm */ inline static struct sos_blockdev_instance * get_blockdev_of_vr(struct sos_umem_vmm_vr * vr) { struct sos_umem_vmm_mapped_resource *mr = sos_umem_vmm_get_mapped_resource_of_vr(vr); return (struct sos_blockdev_instance *)mr->custom_data; } static void blockdev_map_ref(struct sos_umem_vmm_vr * vr) { struct sos_blockdev_instance * blockdev = get_blockdev_of_vr(vr); blockdev_use_instance(blockdev); } static void blockdev_map_unref(struct sos_umem_vmm_vr * vr) { struct sos_blockdev_instance * blockdev = get_blockdev_of_vr(vr); sos_uaddr_t vr_start = sos_umem_vmm_get_start_of_vr(vr); sos_uaddr_t vr_size = sos_umem_vmm_get_size_of_vr(vr); sos_luoffset_t start_offset = sos_umem_vmm_get_offset_in_resource(vr); sos_ui32_t vr_flags = sos_umem_vmm_get_flags_of_vr(vr); /* For a shared mapping, un-reference the pagecache entries, if any */ if (vr_flags & SOS_VR_MAP_SHARED) { sos_uaddr_t uaddr; for (uaddr = vr_start ; uaddr < vr_start + vr_size ; uaddr += SOS_PAGE_SIZE) { sos_paddr_t paddr; sos_luoffset_t blockdev_offset; paddr = sos_paging_get_paddr(uaddr); if (! paddr) /* No physical page mapped at this address yet */ continue; /* Unreference the corresponding pagecache entry */ blockdev_offset = SOS_PAGE_ALIGN_INF(uaddr) - vr_start; blockdev_offset += start_offset; /* Flush any change to disk */ if (sos_paging_is_dirty(paddr)) sos_fs_pagecache_set_dirty(blockdev->map_cache, blockdev_offset, FALSE); SOS_ASSERT_FATAL(SOS_OK == sos_fs_pagecache_unref_page(blockdev->map_cache, blockdev_offset)); } } sos_blockdev_release_instance(blockdev); } static sos_ret_t blockdev_map_page_in(struct sos_umem_vmm_vr * vr, sos_uaddr_t uaddr, sos_bool_t write_access) { struct sos_blockdev_instance * blockdev = get_blockdev_of_vr(vr); sos_uaddr_t vr_start = sos_umem_vmm_get_start_of_vr(vr); sos_uaddr_t vr_size = sos_umem_vmm_get_size_of_vr(vr); sos_luoffset_t start_offset = sos_umem_vmm_get_offset_in_resource(vr); sos_luoffset_t offset_in_device; sos_bool_t read_from_blkcache = TRUE; /**< Need to fill the page from blkcache ? */ sos_vaddr_t kernel_page; /** Use a (temporary) kernel-mapped page to store the contents of the page */ struct sos_fs_pagecache_entry * pagecache_entry = NULL; sos_ret_t retval; SOS_ASSERT_FATAL(vr_size > 0); /* Make sure the access is within the block device limits */ offset_in_device = uaddr - vr_start + sizeof(int) - 1; offset_in_device += start_offset; offset_in_device /= blockdev->block_size; if (offset_in_device >= blockdev->number_of_blocks) return -SOS_EFAULT; /* * We have to map: * - shared mappings: a fresh page from disk if it is not yet in the * page cache * - private mappings: a fresh page from disk (always) */ /* Compute block index of beginning of page */ offset_in_device = SOS_PAGE_ALIGN_INF(uaddr) - vr_start;; offset_in_device += start_offset; if (sos_umem_vmm_get_flags_of_vr(vr) & SOS_VR_MAP_SHARED) { /* This is a shared mapping ! */ sos_bool_t newly_allocated; pagecache_entry = sos_fs_pagecache_ref_page(blockdev->map_cache, offset_in_device, & kernel_page, & newly_allocated); if (! pagecache_entry) return -SOS_EFAULT; if (! newly_allocated) read_from_blkcache = FALSE; } else { /* Allocate a temporary kernel page */ kernel_page = sos_kmem_vmm_alloc(1, SOS_KMEM_VMM_MAP); if ((sos_vaddr_t)NULL == kernel_page) return -SOS_EFAULT; } retval = SOS_OK; /* Need to fill the kernel page ? */ if (read_from_blkcache) { SOS_GENADDR_DECL(gaddr, FALSE, kernel_page); sos_size_t rdlen = SOS_PAGE_SIZE; retval = blockdev_generic_read(blockdev, offset_in_device, gaddr, & rdlen, TRUE); if (SOS_PAGE_SIZE != rdlen) retval = -SOS_EIO; } /* Make sure nobody mapped anything in the meantime */ if ((sos_paddr_t)NULL != sos_paging_get_paddr(uaddr)) retval = -SOS_EBUSY; if (SOS_OK == retval) { sos_paddr_t ppage_paddr = sos_paging_get_paddr(kernel_page); if (0 != ppage_paddr) retval = sos_paging_map(ppage_paddr, SOS_PAGE_ALIGN_INF(uaddr), TRUE, sos_umem_vmm_get_prot_of_vr(vr)); else retval = -SOS_EFAULT; } /* Shared mapping: unlock the entry and eventually remove it from the cache in case of failure of the preceding operations */ if (NULL != pagecache_entry) { sos_fs_pagecache_unlock_page(blockdev->map_cache, pagecache_entry, (SOS_OK != retval)); } /* Private mappings: remove the remporary kernel page */ else sos_kmem_vmm_free((sos_vaddr_t)kernel_page); return retval; } /** * umem_vmm callback for msync */ static sos_ret_t blockdev_map_sync_page(struct sos_umem_vmm_vr * vr, sos_uaddr_t page_uaddr, sos_ui32_t flags) { struct sos_blockdev_instance * blockdev = get_blockdev_of_vr(vr); sos_uaddr_t vr_start = sos_umem_vmm_get_start_of_vr(vr); sos_luoffset_t start_offset = sos_umem_vmm_get_offset_in_resource(vr); sos_luoffset_t offset; /* Make sure the access is within the block device limits */ offset = page_uaddr - vr_start; offset += start_offset; if (offset >= blockdev->number_of_blocks*blockdev->block_size) return -SOS_EFAULT; /* Mark the page as non dirty now */ sos_paging_set_dirty(page_uaddr, FALSE); /* Simply mark the corresponding pagecache entry as dirty */ return sos_fs_pagecache_set_dirty(blockdev->map_cache, offset, flags & SOS_MSYNC_SYNC); } static struct sos_umem_vmm_vr_ops blockdev_map_ops = (struct sos_umem_vmm_vr_ops){ .ref = blockdev_map_ref, .unref = blockdev_map_unref, .page_in = blockdev_map_page_in, .sync_page = blockdev_map_sync_page }; /** Callback called each time an area of the device is mapped into user space */ static sos_ret_t blockdev_new_mapping(struct sos_umem_vmm_vr * vr) { struct sos_blockdev_instance * blockdev = get_blockdev_of_vr(vr); sos_luoffset_t start_offset = sos_umem_vmm_get_offset_in_resource(vr); sos_size_t map_size = sos_umem_vmm_get_size_of_vr(vr); sos_luoffset_t stop_offset; sos_luoffset_t block_index; if (map_size <= 0) return -SOS_EINVAL; /* Make sure request lies inside the device */ stop_offset = start_offset + map_size - 1; block_index = stop_offset / blockdev->block_size; if (block_index >= blockdev->number_of_blocks) return -SOS_EINVAL; return sos_umem_vmm_set_ops_of_vr(vr, &blockdev_map_ops); }