sos-code-article10/sos/blkdev.c

1198 lines
32 KiB
C

/* 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 <sos/list.h>
#include <sos/assert.h>
#include <sos/kmalloc.h>
#include <sos/uaccess.h>
#include <sos/thread.h>
#include <sos/time.h>
#include <sos/blkcache.h>
#include <sos/fs_pagecache.h>
#include <sos/umem_vmm.h>
#include <sos/physmem.h>
#include <hwcore/paging.h>
#include <drivers/devices.h>
#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);
}