1198 lines
32 KiB
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);
|
|
}
|
|
|