/* * 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 #include #include #include #include #include #include #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); }