490 lines
13 KiB
C
490 lines
13 KiB
C
/* 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 <sos/assert.h>
|
|
#include <sos/kmalloc.h>
|
|
#include <sos/physmem.h>
|
|
#include <hwcore/paging.h>
|
|
#include <sos/kmem_slab.h>
|
|
#include <sos/list.h>
|
|
#include <hwcore/paging.h>
|
|
#include <drivers/devices.h>
|
|
#include <sos/kmem_vmm.h>
|
|
#include <sos/uaccess.h>
|
|
#include <sos/chardev.h>
|
|
|
|
#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);
|
|
}
|