446 lines
10 KiB
C
446 lines
10 KiB
C
|
/*
|
||
|
* Copyright (c) 2009 Travis Geiselbrecht
|
||
|
*
|
||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||
|
* a copy of this software and associated documentation files
|
||
|
* (the "Software"), to deal in the Software without restriction,
|
||
|
* including without limitation the rights to use, copy, modify, merge,
|
||
|
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||
|
* and to permit persons to whom the Software is furnished to do so,
|
||
|
* subject to the following conditions:
|
||
|
*
|
||
|
* The above copyright notice and this permission notice shall be
|
||
|
* included in all copies or substantial portions of the Software.
|
||
|
*
|
||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||
|
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||
|
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||
|
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||
|
*/
|
||
|
#include <stdlib.h>
|
||
|
#include <debug.h>
|
||
|
#include <err.h>
|
||
|
#include <string.h>
|
||
|
#include <list.h>
|
||
|
#include <lib/bio.h>
|
||
|
#include <kernel/mutex.h>
|
||
|
|
||
|
#define LOCAL_TRACE 0
|
||
|
|
||
|
struct bdev_struct {
|
||
|
struct list_node list;
|
||
|
mutex_t lock;
|
||
|
};
|
||
|
|
||
|
static struct bdev_struct *bdevs;
|
||
|
|
||
|
/* default implementation is to use the read_block hook to 'deblock' the device */
|
||
|
static ssize_t bio_default_read(struct bdev *dev, void *_buf, off_t offset, size_t len)
|
||
|
{
|
||
|
uint8_t *buf = (uint8_t *)_buf;
|
||
|
ssize_t bytes_read = 0;
|
||
|
bnum_t block;
|
||
|
int err = 0;
|
||
|
STACKBUF_DMA_ALIGN(temp, dev->block_size); // temporary buffer for partial block transfers
|
||
|
|
||
|
/* find the starting block */
|
||
|
block = offset / dev->block_size;
|
||
|
|
||
|
LTRACEF("buf %p, offset %lld, block %u, len %zd\n", buf, offset, block, len);
|
||
|
/* handle partial first block */
|
||
|
if ((offset % dev->block_size) != 0) {
|
||
|
/* read in the block */
|
||
|
err = bio_read_block(dev, temp, block, 1);
|
||
|
if (err < 0)
|
||
|
goto err;
|
||
|
|
||
|
/* copy what we need */
|
||
|
size_t block_offset = offset % dev->block_size;
|
||
|
size_t tocopy = MIN(dev->block_size - block_offset, len);
|
||
|
memcpy(buf, temp + block_offset, tocopy);
|
||
|
|
||
|
/* increment our buffers */
|
||
|
buf += tocopy;
|
||
|
len -= tocopy;
|
||
|
bytes_read += tocopy;
|
||
|
block++;
|
||
|
}
|
||
|
|
||
|
LTRACEF("buf %p, block %u, len %zd\n", buf, block, len);
|
||
|
/* handle middle blocks */
|
||
|
if (len >= dev->block_size) {
|
||
|
/* do the middle reads */
|
||
|
size_t block_count = len / dev->block_size;
|
||
|
err = bio_read_block(dev, buf, block, block_count);
|
||
|
if (err < 0)
|
||
|
goto err;
|
||
|
|
||
|
/* increment our buffers */
|
||
|
size_t bytes = block_count * dev->block_size;
|
||
|
DEBUG_ASSERT(bytes <= len);
|
||
|
|
||
|
buf += bytes;
|
||
|
len -= bytes;
|
||
|
bytes_read += bytes;
|
||
|
block += block_count;
|
||
|
}
|
||
|
|
||
|
LTRACEF("buf %p, block %u, len %zd\n", buf, block, len);
|
||
|
/* handle partial last block */
|
||
|
if (len > 0) {
|
||
|
/* read the block */
|
||
|
err = bio_read_block(dev, temp, block, 1);
|
||
|
if (err < 0)
|
||
|
goto err;
|
||
|
|
||
|
/* copy the partial block from our temp buffer */
|
||
|
memcpy(buf, temp, len);
|
||
|
|
||
|
bytes_read += len;
|
||
|
}
|
||
|
|
||
|
err:
|
||
|
/* return error or bytes read */
|
||
|
return (err >= 0) ? bytes_read : err;
|
||
|
}
|
||
|
|
||
|
static ssize_t bio_default_write(struct bdev *dev, const void *_buf, off_t offset, size_t len)
|
||
|
{
|
||
|
const uint8_t *buf = (const uint8_t *)_buf;
|
||
|
ssize_t bytes_written = 0;
|
||
|
bnum_t block;
|
||
|
int err = 0;
|
||
|
STACKBUF_DMA_ALIGN(temp, dev->block_size); // temporary buffer for partial block transfers
|
||
|
|
||
|
/* find the starting block */
|
||
|
block = offset / dev->block_size;
|
||
|
|
||
|
LTRACEF("buf %p, offset %lld, block %u, len %zd\n", buf, offset, block, len);
|
||
|
/* handle partial first block */
|
||
|
if ((offset % dev->block_size) != 0) {
|
||
|
/* read in the block */
|
||
|
err = bio_read_block(dev, temp, block, 1);
|
||
|
if (err < 0)
|
||
|
goto err;
|
||
|
|
||
|
/* copy what we need */
|
||
|
size_t block_offset = offset % dev->block_size;
|
||
|
size_t tocopy = MIN(dev->block_size - block_offset, len);
|
||
|
memcpy(temp + block_offset, buf, tocopy);
|
||
|
|
||
|
/* write it back out */
|
||
|
err = bio_write_block(dev, temp, block, 1);
|
||
|
if (err < 0)
|
||
|
goto err;
|
||
|
|
||
|
/* increment our buffers */
|
||
|
buf += tocopy;
|
||
|
len -= tocopy;
|
||
|
bytes_written += tocopy;
|
||
|
block++;
|
||
|
}
|
||
|
|
||
|
LTRACEF("buf %p, block %u, len %zd\n", buf, block, len);
|
||
|
/* handle middle blocks */
|
||
|
if (len >= dev->block_size) {
|
||
|
/* do the middle writes */
|
||
|
size_t block_count = len / dev->block_size;
|
||
|
err = bio_write_block(dev, buf, block, block_count);
|
||
|
if (err < 0)
|
||
|
goto err;
|
||
|
|
||
|
/* increment our buffers */
|
||
|
size_t bytes = block_count * dev->block_size;
|
||
|
DEBUG_ASSERT(bytes <= len);
|
||
|
|
||
|
buf += bytes;
|
||
|
len -= bytes;
|
||
|
bytes_written += bytes;
|
||
|
block += block_count;
|
||
|
}
|
||
|
|
||
|
LTRACEF("buf %p, block %u, len %zd\n", buf, block, len);
|
||
|
/* handle partial last block */
|
||
|
if (len > 0) {
|
||
|
/* read the block */
|
||
|
err = bio_read_block(dev, temp, block, 1);
|
||
|
if (err < 0)
|
||
|
goto err;
|
||
|
|
||
|
/* copy the partial block from our temp buffer */
|
||
|
memcpy(temp, buf, len);
|
||
|
|
||
|
/* write it back out */
|
||
|
err = bio_write_block(dev, temp, block, 1);
|
||
|
if (err < 0)
|
||
|
goto err;
|
||
|
|
||
|
bytes_written += len;
|
||
|
}
|
||
|
|
||
|
err:
|
||
|
/* return error or bytes written */
|
||
|
return (err >= 0) ? bytes_written : err;
|
||
|
}
|
||
|
|
||
|
static ssize_t bio_default_erase(struct bdev *dev, off_t offset, size_t len)
|
||
|
{
|
||
|
/* default erase operation is to just write zeros over the device */
|
||
|
#define ERASE_BUF_SIZE 4096
|
||
|
uint8_t *zero_buf;
|
||
|
|
||
|
zero_buf = calloc(1, ERASE_BUF_SIZE);
|
||
|
|
||
|
size_t remaining = len;
|
||
|
off_t pos = offset;
|
||
|
while (remaining > 0) {
|
||
|
ssize_t towrite = MIN(remaining, ERASE_BUF_SIZE);
|
||
|
|
||
|
ssize_t written = bio_write(dev, zero_buf, pos, towrite);
|
||
|
if (written < 0)
|
||
|
return pos;
|
||
|
|
||
|
pos += written;
|
||
|
remaining -= written;
|
||
|
|
||
|
if (written < towrite)
|
||
|
return pos;
|
||
|
}
|
||
|
|
||
|
return len;
|
||
|
}
|
||
|
|
||
|
static ssize_t bio_default_read_block(struct bdev *dev, void *buf, bnum_t block, uint count)
|
||
|
{
|
||
|
panic("%s no reasonable default operation\n", __PRETTY_FUNCTION__);
|
||
|
}
|
||
|
|
||
|
static ssize_t bio_default_write_block(struct bdev *dev, const void *buf, bnum_t block, uint count)
|
||
|
{
|
||
|
panic("%s no reasonable default operation\n", __PRETTY_FUNCTION__);
|
||
|
}
|
||
|
|
||
|
static void bdev_inc_ref(bdev_t *dev)
|
||
|
{
|
||
|
atomic_add(&dev->ref, 1);
|
||
|
}
|
||
|
|
||
|
static void bdev_dec_ref(bdev_t *dev)
|
||
|
{
|
||
|
int oldval = atomic_add(&dev->ref, -1);
|
||
|
if (oldval == 1) {
|
||
|
// last ref, remove it
|
||
|
DEBUG_ASSERT(!list_in_list(&dev->node));
|
||
|
|
||
|
TRACEF("last ref, removing (%s)\n", dev->name);
|
||
|
|
||
|
// call the close hook if it exists
|
||
|
if (dev->close)
|
||
|
dev->close(dev);
|
||
|
|
||
|
free(dev->name);
|
||
|
free(dev);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bdev_t *bio_open(const char *name)
|
||
|
{
|
||
|
bdev_t *bdev = NULL;
|
||
|
|
||
|
/* see if it's in our list */
|
||
|
bdev_t *entry;
|
||
|
mutex_acquire(&bdevs->lock);
|
||
|
list_for_every_entry(&bdevs->list, entry, bdev_t, node) {
|
||
|
DEBUG_ASSERT(entry->ref > 0);
|
||
|
if (!strcmp(entry->name, name)) {
|
||
|
bdev = entry;
|
||
|
bdev_inc_ref(bdev);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
mutex_release(&bdevs->lock);
|
||
|
|
||
|
return bdev;
|
||
|
}
|
||
|
|
||
|
void bio_close(bdev_t *dev)
|
||
|
{
|
||
|
DEBUG_ASSERT(dev);
|
||
|
|
||
|
bdev_dec_ref(dev);
|
||
|
}
|
||
|
|
||
|
ssize_t bio_read(bdev_t *dev, void *buf, off_t offset, size_t len)
|
||
|
{
|
||
|
LTRACEF("dev '%s', buf %p, offset %lld, len %zd\n", dev->name, buf, offset, len);
|
||
|
|
||
|
DEBUG_ASSERT(dev->ref > 0);
|
||
|
|
||
|
/* range check */
|
||
|
if (offset < 0)
|
||
|
return -1;
|
||
|
if (offset >= dev->size)
|
||
|
return 0;
|
||
|
if (len == 0)
|
||
|
return 0;
|
||
|
if (offset + len > dev->size)
|
||
|
len = dev->size - offset;
|
||
|
|
||
|
return dev->read(dev, buf, offset, len);
|
||
|
}
|
||
|
|
||
|
ssize_t bio_read_block(bdev_t *dev, void *buf, bnum_t block, uint count)
|
||
|
{
|
||
|
LTRACEF("dev '%s', buf %p, block %d, count %u\n", dev->name, buf, block, count);
|
||
|
|
||
|
DEBUG_ASSERT(dev->ref > 0);
|
||
|
|
||
|
/* range check */
|
||
|
if (block > dev->block_count)
|
||
|
return 0;
|
||
|
if (count == 0)
|
||
|
return 0;
|
||
|
if (block + count > dev->block_count)
|
||
|
count = dev->block_count - block;
|
||
|
|
||
|
return dev->read_block(dev, buf, block, count);
|
||
|
}
|
||
|
|
||
|
ssize_t bio_write(bdev_t *dev, const void *buf, off_t offset, size_t len)
|
||
|
{
|
||
|
LTRACEF("dev '%s', buf %p, offset %lld, len %zd\n", dev->name, buf, offset, len);
|
||
|
|
||
|
DEBUG_ASSERT(dev->ref > 0);
|
||
|
|
||
|
/* range check */
|
||
|
if (offset < 0)
|
||
|
return -1;
|
||
|
if (offset >= dev->size)
|
||
|
return 0;
|
||
|
if (len == 0)
|
||
|
return 0;
|
||
|
if (offset + len > dev->size)
|
||
|
len = dev->size - offset;
|
||
|
|
||
|
return dev->write(dev, buf, offset, len);
|
||
|
}
|
||
|
|
||
|
ssize_t bio_write_block(bdev_t *dev, const void *buf, bnum_t block, uint count)
|
||
|
{
|
||
|
LTRACEF("dev '%s', buf %p, block %d, count %u\n", dev->name, buf, block, count);
|
||
|
|
||
|
DEBUG_ASSERT(dev->ref > 0);
|
||
|
|
||
|
/* range check */
|
||
|
if (block > dev->block_count)
|
||
|
return 0;
|
||
|
if (count == 0)
|
||
|
return 0;
|
||
|
if (block + count > dev->block_count)
|
||
|
count = dev->block_count - block;
|
||
|
|
||
|
return dev->write_block(dev, buf, block, count);
|
||
|
}
|
||
|
|
||
|
ssize_t bio_erase(bdev_t *dev, off_t offset, size_t len)
|
||
|
{
|
||
|
LTRACEF("dev '%s', offset %lld, len %zd\n", dev->name, offset, len);
|
||
|
|
||
|
DEBUG_ASSERT(dev->ref > 0);
|
||
|
|
||
|
/* range check */
|
||
|
if (offset < 0)
|
||
|
return -1;
|
||
|
if (offset >= dev->size)
|
||
|
return 0;
|
||
|
if (len == 0)
|
||
|
return 0;
|
||
|
if (offset + len > dev->size)
|
||
|
len = dev->size - offset;
|
||
|
|
||
|
return dev->erase(dev, offset, len);
|
||
|
}
|
||
|
|
||
|
int bio_ioctl(bdev_t *dev, int request, void *argp)
|
||
|
{
|
||
|
LTRACEF("dev '%s', request %08x, argp %p\n", dev->name, request, argp);
|
||
|
|
||
|
if (dev->ioctl == NULL) {
|
||
|
return ERR_NOT_SUPPORTED;
|
||
|
} else {
|
||
|
return dev->ioctl(dev, request, argp);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void bio_initialize_bdev(bdev_t *dev, const char *name, size_t block_size, bnum_t block_count)
|
||
|
{
|
||
|
DEBUG_ASSERT(dev);
|
||
|
DEBUG_ASSERT(name);
|
||
|
DEBUG_ASSERT(block_size == 512); // XXX can only deal with 512 for now
|
||
|
|
||
|
list_clear_node(&dev->node);
|
||
|
dev->name = strdup(name);
|
||
|
dev->block_size = block_size;
|
||
|
dev->block_count = block_count;
|
||
|
dev->size = (off_t)block_count * block_size;
|
||
|
dev->ref = 0;
|
||
|
|
||
|
/* set up the default hooks, the sub driver should override the block operations at least */
|
||
|
dev->read = bio_default_read;
|
||
|
dev->read_block = bio_default_read_block;
|
||
|
dev->write = bio_default_write;
|
||
|
dev->write_block = bio_default_write_block;
|
||
|
dev->erase = bio_default_erase;
|
||
|
dev->close = NULL;
|
||
|
}
|
||
|
|
||
|
void bio_register_device(bdev_t *dev)
|
||
|
{
|
||
|
DEBUG_ASSERT(dev);
|
||
|
|
||
|
LTRACEF(" '%s'\n", dev->name);
|
||
|
|
||
|
bdev_inc_ref(dev);
|
||
|
|
||
|
mutex_acquire(&bdevs->lock);
|
||
|
list_add_head(&bdevs->list, &dev->node);
|
||
|
mutex_release(&bdevs->lock);
|
||
|
}
|
||
|
|
||
|
void bio_unregister_device(bdev_t *dev)
|
||
|
{
|
||
|
DEBUG_ASSERT(dev);
|
||
|
|
||
|
LTRACEF(" '%s'\n", dev->name);
|
||
|
|
||
|
// remove it from the list
|
||
|
mutex_acquire(&bdevs->lock);
|
||
|
list_delete(&dev->node);
|
||
|
mutex_release(&bdevs->lock);
|
||
|
|
||
|
bdev_dec_ref(dev); // remove the ref the list used to have
|
||
|
}
|
||
|
|
||
|
void bio_dump_devices(void)
|
||
|
{
|
||
|
printf("block devices:\n");
|
||
|
bdev_t *entry;
|
||
|
mutex_acquire(&bdevs->lock);
|
||
|
list_for_every_entry(&bdevs->list, entry, bdev_t, node) {
|
||
|
printf("\t%s, size %lld, bsize %zd, ref %d\n", entry->name, entry->size, entry->block_size, entry->ref);
|
||
|
}
|
||
|
mutex_release(&bdevs->lock);
|
||
|
}
|
||
|
|
||
|
void bio_init(void)
|
||
|
{
|
||
|
bdevs = malloc(sizeof(*bdevs));
|
||
|
|
||
|
list_initialize(&bdevs->list);
|
||
|
mutex_init(&bdevs->lock);
|
||
|
}
|
||
|
|