/* Copyright (C) 2005 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 "mem.h" /** * A mapped mem/kmem resource */ struct kernel_remapped_resource { int ref_cnt; struct sos_umem_vmm_mapped_resource mr; }; /** Called after the virtual region has been inserted inside its address space */ static void resource_ref(struct sos_umem_vmm_vr * vr) { /* Retrieve the mem/kmem structure associated with the mapped resource */ struct kernel_remapped_resource * resource; resource = (struct kernel_remapped_resource*) sos_umem_vmm_get_mapped_resource_of_vr(vr)->custom_data; /* Increment ref counter */ resource->ref_cnt ++; } /** Called when the virtual region is removed from its address space */ static void resource_unref(struct sos_umem_vmm_vr * vr) { /* Retrieve the mem/kmem structure associated with the mapped resource */ struct kernel_remapped_resource * resource; resource = (struct kernel_remapped_resource*) sos_umem_vmm_get_mapped_resource_of_vr(vr)->custom_data; /* Decrement ref coutner */ SOS_ASSERT_FATAL(resource->ref_cnt > 0); resource->ref_cnt --; /* Free the resource if it becomes unused */ if (resource->ref_cnt == 0) sos_kfree((sos_vaddr_t)resource); } /** MOST IMPORTANT callback ! Called when a thread page faults on the resource's mapping */ static sos_ret_t kmem_page_in(struct sos_umem_vmm_vr * vr, sos_uaddr_t uaddr, sos_bool_t write_access) { sos_vaddr_t vaddr; sos_ret_t retval = SOS_OK; sos_paddr_t ppage_paddr; /* Compute address of kernel page */ vaddr = uaddr - sos_umem_vmm_get_start_of_vr(vr) + sos_umem_vmm_get_offset_in_resource(vr); /* Don't allow demand paging of non kernel pages */ if (! SOS_PAGING_IS_KERNEL_AREA(vaddr, 1)) return -SOS_EFAULT; /* Lookup physical kernel page */ ppage_paddr = sos_paging_get_paddr(SOS_PAGE_ALIGN_INF(vaddr)); /* Cannot access unmapped kernel pages */ if (! ppage_paddr) return -SOS_EFAULT; /* Remap it in user space */ retval = sos_paging_map(ppage_paddr, SOS_PAGE_ALIGN_INF(uaddr), TRUE, sos_umem_vmm_get_prot_of_vr(vr)); return retval; } /** The callbacks for a mapped kmem resource */ static struct sos_umem_vmm_vr_ops kmem_ops = (struct sos_umem_vmm_vr_ops) { .ref = resource_ref, .unref = resource_unref, .page_in = kmem_page_in, }; /** The callback that gets called when the resource gets mapped */ static sos_ret_t kmem_mmap(struct sos_umem_vmm_vr *vr) { return sos_umem_vmm_set_ops_of_vr(vr, &kmem_ops); } /** The function responsible for mapping the /dev/kmem resource in user space */ static sos_ret_t sos_dev_kmem_map(struct sos_umem_vmm_as * dest_as, sos_uaddr_t *uaddr, sos_size_t size, sos_vaddr_t offset, sos_ui32_t access_rights, sos_ui32_t flags) { sos_ret_t retval; struct kernel_remapped_resource * kmem_resource; /* Allocate a new "descriptor" for the resource */ kmem_resource = (struct kernel_remapped_resource*) sos_kmalloc(sizeof(*kmem_resource), 0); if (! kmem_resource) return -SOS_ENOMEM; memset(kmem_resource, 0x0, sizeof(*kmem_resource)); kmem_resource->mr.allowed_access_rights = SOS_VM_MAP_PROT_READ | SOS_VM_MAP_PROT_WRITE | SOS_VM_MAP_PROT_EXEC; kmem_resource->mr.custom_data = kmem_resource; kmem_resource->mr.mmap = kmem_mmap; /* Map it in user space */ retval = sos_umem_vmm_map(dest_as, uaddr, size, access_rights, flags, & kmem_resource->mr, offset); if (SOS_OK != retval) { sos_kfree((sos_vaddr_t)kmem_resource); return retval; } return SOS_OK; } /** MOST IMPORTANT callback ! Called when a thread page faults on the resource's mapping */ static sos_ret_t physmem_page_in(struct sos_umem_vmm_vr * vr, sos_uaddr_t uaddr, sos_bool_t write_access) { sos_ret_t retval = SOS_OK; sos_paddr_t ppage_paddr; /* Compute address of kernel page */ ppage_paddr = uaddr - sos_umem_vmm_get_start_of_vr(vr) + sos_umem_vmm_get_offset_in_resource(vr); /* Remap page in user space */ retval = sos_paging_map(SOS_PAGE_ALIGN_INF(ppage_paddr), SOS_PAGE_ALIGN_INF(uaddr), TRUE, sos_umem_vmm_get_prot_of_vr(vr)); return retval; } /** The callbacks for a mapped physmem resource */ static struct sos_umem_vmm_vr_ops physmem_ops = (struct sos_umem_vmm_vr_ops) { .ref = resource_ref, .unref = resource_unref, .page_in = physmem_page_in, }; /** The callback that gets called when the resource gets mapped */ static sos_ret_t physmem_mmap(struct sos_umem_vmm_vr *vr) { return sos_umem_vmm_set_ops_of_vr(vr, &physmem_ops); } /** The function responsible for mapping the /dev/mem resource in user space */ static sos_ret_t sos_dev_physmem_map(struct sos_umem_vmm_as * dest_as, sos_uaddr_t *uaddr, sos_size_t size, sos_paddr_t offset, sos_ui32_t access_rights, sos_ui32_t flags) { sos_ret_t retval; struct kernel_remapped_resource * physmem_resource; physmem_resource = (struct kernel_remapped_resource*) sos_kmalloc(sizeof(*physmem_resource), 0); if (! physmem_resource) return -SOS_ENOMEM; memset(physmem_resource, 0x0, sizeof(*physmem_resource)); physmem_resource->mr.allowed_access_rights = SOS_VM_MAP_PROT_READ | SOS_VM_MAP_PROT_WRITE | SOS_VM_MAP_PROT_EXEC; physmem_resource->mr.custom_data = physmem_resource; physmem_resource->mr.mmap = physmem_mmap; retval = sos_umem_vmm_map(dest_as, uaddr, size, access_rights, flags, & physmem_resource->mr, offset); if (SOS_OK != retval) { sos_kfree((sos_vaddr_t)physmem_resource); return retval; } return SOS_OK; } /* * /dev/mem and /dev/kmem character device operations * * the "custom_data" field of the FS node is used to store the total * number of pages available */ #define GET_DEV_SIZE(fsnode) \ ((sos_size_t)(fsnode)->custom_data) static sos_ret_t dev_mem_fs_open(struct sos_fs_node * fsnode, struct sos_fs_opened_file * of, void * chardev_class_custom_data) { /* Make sure the device is supported by this driver and compute its "size" (use the custom_data field to store it) */ switch (fsnode->dev_id.device_instance) { /* For /dev/kmem, go to the end of the kernel mapping */ case SOS_CHARDEV_KMEM_MINOR: fsnode->custom_data = (void*)SOS_PAGING_BASE_USER_ADDRESS; return SOS_OK; break; /* For /dev/mem, go to the end of physical memory */ case SOS_CHARDEV_PHYSMEM_MINOR: { sos_size_t ram_pages = 0; sos_physmem_get_state(& ram_pages, NULL); fsnode->custom_data = (void*)(ram_pages << SOS_PAGE_SHIFT); } return SOS_OK; break; default: break; } return -SOS_ENODEV; } static sos_ret_t dev_mem_fs_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 = GET_DEV_SIZE(fsnode); *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; } typedef enum { DO_READ, DO_WRITE } dev_mem_access_type_t; static sos_ret_t dev_mem_fs_access(struct sos_fs_opened_file *this, sos_uaddr_t user_buf, sos_size_t * /* in/out */len, dev_mem_access_type_t access_type) { struct sos_fs_node * fsnode = sos_fs_nscache_get_fs_node(this->direntry); sos_vaddr_t physmem_transfer_kernel_page = 0; /* Used for /dev/mem only */ sos_uoffset_t offs; sos_size_t accesslen = 0; /* Readjust copy length to match the size of the device */ if (this->position + *len >= GET_DEV_SIZE(fsnode)) *len = GET_DEV_SIZE(fsnode) - this->position; /* Ignore zero-size requests */ if (*len <= 0) return SOS_OK; /* For /dev/mem device, prepare a kernel page to copy the physical pages before transferring to user space */ if (SOS_CHARDEV_PHYSMEM_MINOR == fsnode->dev_id.device_instance) { physmem_transfer_kernel_page = sos_kmem_vmm_alloc(1, 0); if (! physmem_transfer_kernel_page) return -SOS_ENOMEM; } /* Try to copy the data in page-size chunks */ offs = this->position; while (offs < this->position + *len) { /* Retrieve page address of data in kernel memory */ sos_uoffset_t page_boundary = SOS_PAGE_ALIGN_INF(offs); sos_vaddr_t page_vaddr; sos_uoffset_t offset_in_page; sos_uoffset_t accesslen_in_page; sos_ret_t retval; /* For /dev/mem device, we need to map the page in kernel memory before */ if (SOS_CHARDEV_PHYSMEM_MINOR == fsnode->dev_id.device_instance) { retval = sos_paging_map(page_boundary, physmem_transfer_kernel_page, FALSE, (access_type==DO_WRITE)? SOS_VM_MAP_PROT_WRITE :SOS_VM_MAP_PROT_READ); if (SOS_OK != retval) break; page_vaddr = physmem_transfer_kernel_page; } /* For /dev/kmem device, the page should already be in kernel space */ else if (! sos_kmem_vmm_is_valid_vaddr(page_boundary)) break; /* No: page is not mapped in kernel space ! */ else page_vaddr = page_boundary; /* Yes, page is mapped */ /* Now copy the data from kernel to user space */ offset_in_page = offs - page_boundary; accesslen_in_page = SOS_PAGE_SIZE - offset_in_page; if (accesslen + accesslen_in_page > *len) accesslen_in_page = *len - accesslen; if (access_type==DO_WRITE) retval = sos_memcpy_from_user(page_vaddr + offset_in_page, user_buf + accesslen, accesslen_in_page); else retval = sos_memcpy_to_user(user_buf + accesslen, page_vaddr + offset_in_page, accesslen_in_page); /* Now, for /dev/mem, unmap the page from kernel */ if (SOS_CHARDEV_PHYSMEM_MINOR == fsnode->dev_id.device_instance) sos_paging_unmap(physmem_transfer_kernel_page); /* Go to next page if possible */ if (retval < 0) break; accesslen += retval; /* If transfer was interrupted, stop here */ if (retval < (sos_ret_t)accesslen_in_page) break; /* Go on to next page */ offs = page_boundary + SOS_PAGE_SIZE; } /* Release the temporary page for physical mem transfers */ if (SOS_CHARDEV_PHYSMEM_MINOR == fsnode->dev_id.device_instance) sos_kmem_vmm_free(physmem_transfer_kernel_page); /* Update the position in the "file" */ *len = accesslen; this->position += accesslen; return SOS_OK; } static sos_ret_t dev_mem_fs_read(struct sos_fs_opened_file *this, sos_uaddr_t dest_buf, sos_size_t * /* in/out */len) { return dev_mem_fs_access(this, dest_buf, len, DO_READ); } static sos_ret_t dev_mem_fs_write(struct sos_fs_opened_file *this, sos_uaddr_t src_buf, sos_size_t * /* in/out */len) { return dev_mem_fs_access(this, src_buf, len, DO_WRITE); } static sos_ret_t dev_mem_fs_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); if (SOS_CHARDEV_PHYSMEM_MINOR == fsnode->dev_id.device_instance) return sos_dev_physmem_map(sos_process_get_address_space(this->owner), uaddr, size, offset, access_rights, flags); return sos_dev_kmem_map(sos_process_get_address_space(this->owner), uaddr, size, offset, access_rights, flags); } static struct sos_chardev_ops dev_mem_fs_ops = (struct sos_chardev_ops) { .open = dev_mem_fs_open, .close = NULL, .seek = dev_mem_fs_seek, .read = dev_mem_fs_read, .write = dev_mem_fs_write, .mmap = dev_mem_fs_mmap, .fcntl = NULL, .ioctl = NULL }; sos_ret_t sos_dev_mem_chardev_setup() { return sos_chardev_register_class(SOS_CHARDEV_MEM_MAJOR, & dev_mem_fs_ops, NULL); }