[Feature]add MT2731_MP2_MR2_SVN388 baseline version
Change-Id: Ief04314834b31e27effab435d3ca8ba33b499059
diff --git a/src/bsp/lk/lib/bio/bio.c b/src/bsp/lk/lib/bio/bio.c
new file mode 100644
index 0000000..6fcd6d8
--- /dev/null
+++ b/src/bsp/lk/lib/bio/bio.c
@@ -0,0 +1,647 @@
+/*
+ * Copyright (c) 2009-2015 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 <trace.h>
+#include <err.h>
+#include <string.h>
+#include <assert.h>
+#include <list.h>
+#include <pow2.h>
+#include <lib/bio.h>
+#include <kernel/mutex.h>
+#include <lk/init.h>
+
+#define LOCAL_TRACE 0
+
+static struct {
+ struct list_node list;
+ mutex_t lock;
+} bdevs = {
+ .list = LIST_INITIAL_VALUE(bdevs.list),
+ .lock = MUTEX_INITIAL_VALUE(bdevs.lock),
+};
+
+/* 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;
+ ssize_t err = 0;
+ uint8_t *temp;
+
+ // temporary buffer for partial block transfers
+ temp = memalign(CACHE_LINE, dev->block_size);
+ if (temp == NULL)
+ return ERR_NO_MEMORY;
+
+ /* 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;
+ } else if ((size_t)err != dev->block_size) {
+ err = ERR_IO;
+ 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);
+
+ // If the device requires alignment AND our buffer is not alread aligned.
+ bool requires_alignment =
+ (dev->flags & BIO_FLAG_CACHE_ALIGNED_READS) &&
+ (IS_ALIGNED((size_t)buf, CACHE_LINE) == false);
+ /* handle middle blocks */
+ if (requires_alignment) {
+ while (len >= dev->block_size) {
+ /* do the middle reads */
+ err = bio_read_block(dev, temp, block, 1);
+ if (err < 0) {
+ goto err;
+ } else if ((size_t)err != dev->block_size) {
+ err = ERR_IO;
+ goto err;
+ }
+ memcpy(buf, temp, dev->block_size);
+
+ buf += dev->block_size;
+ len -= dev->block_size;
+ bytes_read += dev->block_size;
+ block++;
+ }
+ } else {
+ uint32_t num_blocks = divpow2(len, dev->block_shift);
+ err = bio_read_block(dev, buf, block, num_blocks);
+ if (err < 0) {
+ goto err;
+ } else if ((size_t)err != dev->block_size * num_blocks) {
+ err = ERR_IO;
+ goto err;
+ }
+ buf += err;
+ len -= err;
+ bytes_read += err;
+ block += num_blocks;
+ }
+
+ 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;
+ } else if ((size_t)err != dev->block_size) {
+ err = ERR_IO;
+ goto err;
+ }
+
+ /* copy the partial block from our temp buffer */
+ memcpy(buf, temp, len);
+
+ bytes_read += len;
+ }
+
+err:
+ free(temp);
+
+ /* 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;
+ ssize_t err = 0;
+ uint8_t *temp;
+
+ // temporary buffer for partial block transfers
+ temp = memalign(CACHE_LINE, dev->block_size);
+ if (temp == NULL)
+ return ERR_NO_MEMORY;
+
+ /* 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;
+ } else if ((size_t)err != dev->block_size) {
+ err = ERR_IO;
+ 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;
+ } else if ((size_t)err != dev->block_size) {
+ err = ERR_IO;
+ goto err;
+ }
+
+ /* increment our buffers */
+ buf += tocopy;
+ len -= tocopy;
+ bytes_written += tocopy;
+ block++;
+ }
+
+ LTRACEF("buf %p, block %u, len %zd\n", buf, block, len);
+
+ // If the device requires alignment AND our buffer is not alread aligned.
+ bool requires_alignment =
+ (dev->flags & BIO_FLAG_CACHE_ALIGNED_WRITES) &&
+ (IS_ALIGNED((size_t)buf, CACHE_LINE) == false);
+
+ /* handle middle blocks */
+ if (requires_alignment) {
+ while (len >= dev->block_size) {
+ /* do the middle reads */
+ memcpy(temp, buf, dev->block_size);
+ err = bio_write_block(dev, temp, block, 1);
+ if (err < 0) {
+ goto err;
+ } else if ((size_t)err != dev->block_size) {
+ err = ERR_IO;
+ goto err;
+ }
+
+ buf += dev->block_size;
+ len -= dev->block_size;
+ bytes_written += dev->block_size;
+ block++;
+ }
+ } else {
+ uint32_t block_count = divpow2(len, dev->block_shift);
+ err = bio_write_block(dev, buf, block, block_count);
+ if (err < 0) {
+ goto err;
+ } else if ((size_t)err != dev->block_size * block_count) {
+ err = ERR_IO;
+ goto err;
+ }
+
+ DEBUG_ASSERT((size_t)err == (block_count * dev->block_size));
+
+ buf += err;
+ len -= err;
+ bytes_written += err;
+ 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;
+ } else if ((size_t)err != dev->block_size) {
+ err = ERR_IO;
+ 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;
+ } else if ((size_t)err != dev->block_size) {
+ err = ERR_IO;
+ goto err;
+ }
+
+ bytes_written += len;
+ }
+
+err:
+ free(temp);
+
+ /* 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 */
+ uint8_t *erase_buf;
+
+ erase_buf = memalign(CACHE_LINE, dev->block_size);
+ if (erase_buf == NULL)
+ return ERR_NO_MEMORY;
+
+ memset(erase_buf, dev->erase_byte, dev->block_size);
+
+ ssize_t erased = 0;
+ size_t remaining = len;
+ off_t pos = offset;
+ while (remaining > 0) {
+ size_t towrite = MIN(remaining, dev->block_size);
+
+ ssize_t written = bio_write(dev, erase_buf, pos, towrite);
+ if (written < 0) {
+ free(erase_buf);
+ return written;
+ }
+
+ erased += written;
+ pos += written;
+ remaining -= written;
+
+ if ((size_t)written < towrite)
+ break;
+ }
+
+ free(erase_buf);
+ return erased;
+}
+
+static ssize_t bio_default_read_block(struct bdev *dev, void *buf, bnum_t block, uint count)
+{
+ return ERR_NOT_SUPPORTED;
+}
+
+static ssize_t bio_default_write_block(struct bdev *dev, const void *buf, bnum_t block, uint count)
+{
+ return ERR_NOT_SUPPORTED;
+}
+
+static void bdev_inc_ref(bdev_t *dev)
+{
+ LTRACEF("Add ref \"%s\" %d -> %d\n", dev->name, dev->ref, dev->ref + 1);
+
+#if WITH_KERNEL_VM
+ atomic_add(&dev->ref, 1);
+#else
+ dev->ref++;
+#endif
+
+}
+
+static void bdev_dec_ref(bdev_t *dev)
+{
+#if WITH_KERNEL_VM
+ int oldval = atomic_add(&dev->ref, -1);
+#else
+ int oldval = dev->ref--;
+#endif
+
+ LTRACEF("Dec ref \"%s\" %d -> %d\n", dev->name, oldval, dev->ref);
+
+ 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);
+ }
+}
+
+size_t bio_trim_range(const bdev_t *dev, off_t offset, size_t len)
+{
+ /* range check */
+ if (offset < 0)
+ return 0;
+ if (offset >= dev->total_size)
+ return 0;
+ if (len == 0)
+ return 0;
+ if ((off_t)(offset + len) > dev->total_size)
+ len = dev->total_size - offset;
+
+ return len;
+}
+
+uint bio_trim_block_range(const bdev_t *dev, bnum_t block, uint count)
+{
+ if (block > dev->block_count)
+ return 0;
+ if (count == 0)
+ return 0;
+ if (block + count > dev->block_count)
+ count = dev->block_count - block;
+
+ return count;
+}
+
+bdev_t *bio_open(const char *name)
+{
+ bdev_t *bdev = NULL;
+
+ LTRACEF(" '%s'\n", name);
+
+ /* 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);
+ LTRACEF(" '%s'\n", dev->name);
+ bdev_dec_ref(dev);
+}
+
+bdev_t *bio_open_by_label(const char *label)
+{
+ 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 (entry->label && !strcmp(entry->label, label)) {
+ bdev = entry;
+ bdev_inc_ref(bdev);
+ break;
+ }
+ }
+ mutex_release(&bdevs.lock);
+
+ return bdev;
+}
+
+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 && dev->ref > 0);
+ DEBUG_ASSERT(buf);
+
+ /* range check */
+ len = bio_trim_range(dev, offset, len);
+ if (len == 0)
+ return 0;
+
+ 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 && dev->ref > 0);
+ DEBUG_ASSERT(buf);
+
+ /* range check */
+ count = bio_trim_block_range(dev, block, count);
+ if (count == 0)
+ return 0;
+
+ 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 && dev->ref > 0);
+ DEBUG_ASSERT(buf);
+
+ /* range check */
+ len = bio_trim_range(dev, offset, len);
+ if (len == 0)
+ return 0;
+
+ 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 && dev->ref > 0);
+ DEBUG_ASSERT(buf);
+
+ /* range check */
+ count = bio_trim_block_range(dev, block, count);
+ if (count == 0)
+ return 0;
+
+ 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 && dev->ref > 0);
+
+ /* range check */
+ len = bio_trim_range(dev, offset, len);
+ if (len == 0)
+ return 0;
+
+ 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,
+ size_t geometry_count,
+ const bio_erase_geometry_info_t* geometry,
+ const uint32_t flags)
+{
+ DEBUG_ASSERT(dev);
+ DEBUG_ASSERT(name);
+
+ // Block size must be finite powers of 2
+ DEBUG_ASSERT(block_size && ispow2(block_size));
+
+ list_clear_node(&dev->node);
+ dev->name = strdup(name);
+ dev->block_size = block_size;
+ dev->block_count = block_count;
+ dev->block_shift = log2_uint(block_size);
+ dev->total_size = (off_t)block_count << dev->block_shift;
+ dev->geometry_count = geometry_count;
+ dev->geometry = geometry;
+ dev->erase_byte = 0;
+ dev->ref = 0;
+ dev->label = NULL;
+ dev->is_gpt = false;
+ dev->flags = flags;
+
+#if DEBUG
+ // If we have been supplied information about our erase geometry, sanity
+ // check it in debug bulids.
+ if (geometry_count && geometry) {
+ for (size_t i = 0; i < geometry_count; ++i) {
+ bio_erase_geometry_info_t* info = geometry + i;
+
+ // Erase sizes must be powers of two and agree with the supplied erase shift.
+ DEBUG_ASSERT(info->erase_size);
+ DEBUG_ASSERT(info->erase_size == ((size_t)1 << info->erase_shift));
+
+ info->start = desc->start;
+ info->erase_size = desc->erase_size;
+ info->erase_shift = log2_uint(desc->erase_size);
+ info->size = ((off_t)desc->block_count) << desc->block_size;
+
+ // Make sure that region is aligned on both a program and erase block boundary.
+ DEBUG_ASSERT(!(info->start & (((off_t)1 << info->block_shift) - 1)));
+ DEBUG_ASSERT(!(info->start & (((off_t)1 << info->erase_shift) - 1)));
+
+ // Make sure that region's length is an integral multiple of both the
+ // program and erase block size.
+ DEBUG_ASSERT(!(info->size & (((off_t)1 << dev->block_shift) - 1)));
+ DEBUG_ASSERT(!(info->size & (((off_t)1 << info->erase_shift) - 1)));
+ }
+
+ // Make sure that none of the regions overlap each other and that they are
+ // listed in ascending order.
+ for (size_t i = 0; (i + 1) < geometry_count; ++i) {
+ bio_geometry_info_t* r1 = dev->geometry + i;
+ bio_geometry_info_t* r2 = dev->geometry + i + 1;
+ DEBUG_ASSERT(r1->start <= r2->start);
+
+ for (size_t j = (i + 1); j < geometry_count; ++j) {
+ bio_geometry_info_t* r2 = dev->geometry + j;
+ DEBUG_ASSERT(!bio_does_overlap(r1->start, r1->size, r2->start, r2->size));
+ }
+ }
+ }
+#endif
+
+ /* 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_tail(&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, label %s",
+ entry->name, entry->total_size, entry->block_size, entry->ref, entry->label);
+
+ if (!entry->geometry_count || !entry->geometry) {
+ printf(" (no erase geometry)\n");
+ } else {
+ for (size_t i = 0; i < entry->geometry_count; ++i) {
+ const bio_erase_geometry_info_t* geo = entry->geometry + i;
+ printf("\n\t\terase_region[%zu] : start %lld size %lld erase size %zu",
+ i, geo->start, geo->size, geo->erase_size);
+
+ }
+ }
+
+ printf("\n");
+ }
+ mutex_release(&bdevs.lock);
+}
diff --git a/src/bsp/lk/lib/bio/debug.c b/src/bsp/lk/lib/bio/debug.c
new file mode 100644
index 0000000..f5c5697
--- /dev/null
+++ b/src/bsp/lk/lib/bio/debug.c
@@ -0,0 +1,457 @@
+/*
+ * Copyright (c) 2009-2014 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 <assert.h>
+#include <debug.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <err.h>
+#include <lib/console.h>
+#include <lib/bio.h>
+#include <lib/partition.h>
+#include <platform.h>
+#include <kernel/thread.h>
+
+#if WITH_LIB_CKSUM
+#include <lib/cksum.h>
+#endif
+
+#define DMA_ALIGNMENT (CACHE_LINE)
+#define THREE_BYTE_ADDR_BOUNDARY (16777216)
+#define SUB_ERASE_TEST_SAMPLES (32)
+
+#if defined(WITH_LIB_CONSOLE)
+
+#if LK_DEBUGLEVEL > 0
+static int cmd_bio(int argc, const cmd_args *argv);
+static int bio_test_device(bdev_t* device);
+
+STATIC_COMMAND_START
+STATIC_COMMAND("bio", "block io debug commands", &cmd_bio)
+STATIC_COMMAND_END(bio);
+
+static int cmd_bio(int argc, const cmd_args *argv)
+{
+ int rc = 0;
+
+ if (argc < 2) {
+notenoughargs:
+ printf("not enough arguments:\n");
+usage:
+ printf("%s list\n", argv[0].str);
+ printf("%s read <device> <address> <offset> <len>\n", argv[0].str);
+ printf("%s write <device> <address> <offset> <len>\n", argv[0].str);
+ printf("%s dump <device> <offset> <len>\n", argv[0].str);
+ printf("%s erase <device> <offset> <len>\n", argv[0].str);
+ printf("%s ioctl <device> <request> <arg>\n", argv[0].str);
+ printf("%s remove <device>\n", argv[0].str);
+ printf("%s test <device>\n", argv[0].str);
+#if WITH_LIB_PARTITION
+ printf("%s partscan <device> [offset]\n", argv[0].str);
+#endif
+#if WITH_LIB_CKSUM
+ printf("%s crc32 <device> <offset> <len> [repeat]\n", argv[0].str);
+#endif
+ return -1;
+ }
+
+ if (!strcmp(argv[1].str, "list")) {
+ bio_dump_devices();
+ } else if (!strcmp(argv[1].str, "read")) {
+ if (argc < 6) goto notenoughargs;
+
+ addr_t address = argv[3].u;
+ off_t offset = argv[4].u; // XXX use long
+ size_t len = argv[5].u;
+
+ bdev_t *dev = bio_open(argv[2].str);
+ if (!dev) {
+ printf("error opening block device\n");
+ return -1;
+ }
+
+ lk_time_t t = current_time();
+ ssize_t err = bio_read(dev, (void *)address, offset, len);
+ t = current_time() - t;
+ dprintf(INFO, "bio_read returns %d, took %u msecs (%d bytes/sec)\n", (int)err, (uint)t, (uint32_t)((uint64_t)err * 1000 / t));
+
+ bio_close(dev);
+
+ rc = err;
+ } else if (!strcmp(argv[1].str, "write")) {
+ if (argc < 6) goto notenoughargs;
+
+ addr_t address = argv[3].u;
+ off_t offset = argv[4].u; // XXX use long
+ size_t len = argv[5].u;
+
+ bdev_t *dev = bio_open(argv[2].str);
+ if (!dev) {
+ printf("error opening block device\n");
+ return -1;
+ }
+
+ lk_time_t t = current_time();
+ ssize_t err = bio_write(dev, (void *)address, offset, len);
+ t = current_time() - t;
+ dprintf(INFO, "bio_write returns %d, took %u msecs (%d bytes/sec)\n", (int)err, (uint)t, (uint32_t)((uint64_t)err * 1000 / t));
+
+ bio_close(dev);
+
+ rc = err;
+ } else if (!strcmp(argv[1].str, "dump")) {
+ if (argc < 5) {
+ printf("not enough arguments:\n");
+ goto usage;
+ }
+
+ off_t offset = argv[3].u; // XXX use long
+ size_t len = argv[4].u;
+
+ bdev_t *dev = bio_open(argv[2].str);
+ if (!dev) {
+ printf("error opening block device\n");
+ return -1;
+ }
+
+ uint8_t* buf = memalign(CACHE_LINE, 256);
+ ssize_t err = 0;
+ while (len > 0) {
+ size_t amt = MIN(256, len);
+ ssize_t err = bio_read(dev, buf, offset, amt);
+
+ if (err < 0) {
+ dprintf(ALWAYS, "read error %s %zu@%zu (err %d)\n",
+ argv[2].str, amt, (size_t)offset, (int)err);
+ break;
+ }
+
+ DEBUG_ASSERT((size_t)err <= amt);
+ hexdump8_ex(buf, err, offset);
+
+ if ((size_t)err != amt) {
+ dprintf(ALWAYS, "short read from %s @%zu (wanted %zu, got %zu)\n",
+ argv[2].str, (size_t)offset, amt, (size_t)err);
+ break;
+ }
+
+ offset += amt;
+ len -= amt;
+ }
+
+ bio_close(dev);
+ rc = err;
+ } else if (!strcmp(argv[1].str, "erase")) {
+ if (argc < 5) goto notenoughargs;
+
+ off_t offset = argv[3].u; // XXX use long
+ size_t len = argv[4].u;
+
+ bdev_t *dev = bio_open(argv[2].str);
+ if (!dev) {
+ printf("error opening block device\n");
+ return -1;
+ }
+
+ lk_time_t t = current_time();
+ ssize_t err = bio_erase(dev, offset, len);
+ t = current_time() - t;
+ dprintf(INFO, "bio_erase returns %d, took %u msecs (%d bytes/sec)\n", (int)err, (uint)t, (uint32_t)((uint64_t)err * 1000 / t));
+
+ bio_close(dev);
+
+ rc = err;
+ } else if (!strcmp(argv[1].str, "ioctl")) {
+ if (argc < 4) goto notenoughargs;
+
+ int request = argv[3].u;
+ int arg = (argc == 5) ? argv[4].u : 0;
+
+ bdev_t *dev = bio_open(argv[2].str);
+ if (!dev) {
+ printf("error opening block device\n");
+ return -1;
+ }
+
+ int err = bio_ioctl(dev, request, (void *)(uintptr_t)arg);
+ dprintf(INFO, "bio_ioctl returns %d\n", err);
+
+ bio_close(dev);
+
+ rc = err;
+ } else if (!strcmp(argv[1].str, "remove")) {
+ if (argc < 3) goto notenoughargs;
+
+ bdev_t *dev = bio_open(argv[2].str);
+ if (!dev) {
+ printf("error opening block device\n");
+ return -1;
+ }
+
+ bio_unregister_device(dev);
+ bio_close(dev);
+ } else if (!strcmp(argv[1].str, "test")) {
+ if (argc < 3) goto notenoughargs;
+
+ bdev_t *dev = bio_open(argv[2].str);
+ if (!dev) {
+ printf("error opening block device\n");
+ return -1;
+ }
+
+ int err = bio_test_device(dev);
+ bio_close(dev);
+
+ rc = err;
+#if WITH_LIB_PARTITION
+ } else if (!strcmp(argv[1].str, "partscan")) {
+ if (argc < 3) goto notenoughargs;
+
+ off_t offset = 0;
+ if (argc > 3)
+ offset = argv[3].u;
+
+ rc = partition_publish(argv[2].str, offset);
+ dprintf(INFO, "partition_publish returns %d\n", rc);
+#endif
+#if WITH_LIB_CKSUM
+ } else if (!strcmp(argv[1].str, "crc32")) {
+ if (argc < 5) goto notenoughargs;
+
+ off_t offset = argv[3].u; // XXX use long
+ size_t len = argv[4].u;
+
+ bdev_t *dev = bio_open(argv[2].str);
+ if (!dev) {
+ printf("error opening block device\n");
+ return -1;
+ }
+
+ void *buf = malloc(dev->block_size);
+
+ bool repeat = false;
+ if (argc >= 6 && !strcmp(argv[5].str, "repeat")) {
+ repeat = true;
+ }
+
+ do {
+ ulong crc = 0;
+ off_t pos = offset;
+ while (pos < offset + (off_t)len) {
+ ssize_t err = bio_read(dev, buf, pos, MIN(len - (pos - offset), dev->block_size));
+
+ if (err <= 0) {
+ printf("error reading at offset 0x%llx\n", offset + pos);
+ break;
+ }
+
+ crc = crc32(crc, buf, err);
+
+ pos += err;
+ }
+
+ printf("crc 0x%08lx\n", crc);
+ } while (repeat);
+
+ bio_close(dev);
+ free(buf);
+
+#endif
+ } else {
+ printf("unrecognized subcommand\n");
+ goto usage;
+ }
+
+ return rc;
+}
+
+#endif
+
+#endif
+
+// Returns the number of blocks that do not match the reference pattern.
+static bool is_valid_block(bdev_t *device, bnum_t block_num, uint8_t* pattern,
+ size_t pattern_length)
+{
+ uint8_t *block_contents = memalign(DMA_ALIGNMENT, device->block_size);
+ if (!block_contents) {
+ return false;
+ }
+
+ ssize_t n_bytes = device->read_block(device, block_contents, block_num, 1);
+ if (n_bytes < 0 || n_bytes != (ssize_t)device->block_size) {
+ free(block_contents);
+ return false;
+ }
+
+ for (size_t i = 0; i < device->block_size; i++) {
+ if (block_contents[i] != pattern[i % pattern_length]) {
+ free(block_contents);
+ block_contents = NULL;
+ return false;
+ }
+ }
+
+ free(block_contents);
+ block_contents = NULL;
+
+ return true;
+}
+
+static ssize_t erase_test(bdev_t *device)
+{
+ printf("erasing device...\n");
+
+ ssize_t err = bio_erase(device, 0, device->total_size);
+ if (err < 0) {
+ return err;
+ } else if (err != device->total_size) {
+ return ERR_IO;
+ }
+
+ printf("validating erase...\n");
+ size_t num_invalid_blocks = 0;
+ for (bnum_t bnum = 0; bnum < device->block_count; bnum++) {
+ if (!is_valid_block(device, bnum, &device->erase_byte, sizeof(device->erase_byte))) {
+ num_invalid_blocks++;
+ }
+ }
+ return num_invalid_blocks;
+}
+
+static bool test_erase_block(bdev_t* device, uint32_t block_addr)
+{
+ bool success = false;
+ uint8_t valid_byte[1];
+
+ uint8_t *block_contents = memalign(DMA_ALIGNMENT, device->block_size);
+ if (!block_contents)
+ return success;
+ memset(block_contents, ~(device->erase_byte), device->block_size);
+
+ ssize_t err = bio_write_block(device, block_contents, block_addr, 1);
+ if (err != (ssize_t)device->block_size) {
+ goto finish;
+ }
+
+ valid_byte[0] = ~(device->erase_byte);
+ if (!is_valid_block(device, block_addr, valid_byte, 1)) {
+ goto finish;
+ }
+
+ err = bio_erase(device, block_addr * device->block_size, 1);
+ if (err <= 0) {
+ goto finish;
+ }
+
+ valid_byte[0] = device->erase_byte;
+ if (is_valid_block(device, block_addr, valid_byte, 1)) {
+ success = true;
+ }
+
+finish:
+ free(block_contents);
+ return success;
+}
+
+// Ensure that (sub)sector erase work.
+static bool sub_erase_test(bdev_t* device, uint32_t n_samples)
+{
+ printf("Sampling the device %d times.\n", n_samples);
+ for (uint32_t i = 0; i < n_samples; i++) {
+ bnum_t block_addr = rand() % device->block_count;
+ if (!test_erase_block(device, block_addr)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static uint8_t get_signature(uint32_t word)
+{
+ uint8_t* sigptr = (uint8_t*)(&word);
+ return sigptr[0] ^ sigptr[1] ^ sigptr[2] ^ sigptr[3];
+}
+
+// returns the number of blocks where the write was not successful.
+static ssize_t write_test(bdev_t *device)
+{
+ uint8_t *test_buffer = memalign(DMA_ALIGNMENT, device->block_size);
+
+ if (!test_buffer)
+ return -1;
+
+ for (bnum_t bnum = 0; bnum < device->block_count; bnum++) {
+ memset(test_buffer, get_signature(bnum), device->block_size);
+ ssize_t err = bio_write_block(device, test_buffer, bnum, 1);
+ if (err < 0) {
+ free(test_buffer);
+ return err;
+ }
+ }
+
+ size_t num_errors = 0;
+ uint8_t expected_pattern[1];
+ for (bnum_t bnum = 0; bnum < device->block_count; bnum++) {
+ expected_pattern[0] = get_signature(bnum);
+ if (!is_valid_block(device, bnum, expected_pattern, sizeof(expected_pattern))) {
+ num_errors++;
+ }
+ }
+
+ free(test_buffer);
+
+ return num_errors;
+}
+
+static int bio_test_device(bdev_t* device)
+{
+ ssize_t num_errors = erase_test(device);
+ if (num_errors < 0) {
+ printf("error %ld performing erase test\n", num_errors);
+ return -1;
+ }
+ printf("discovered %ld error(s) while testing erase.\n", num_errors);
+ if (num_errors) {
+ // No point in continuing the tests if we couldn't erase the device.
+ printf("not continuing to test writes.\n");
+ return -1;
+ }
+
+ num_errors = write_test(device);
+ printf("Discovered %ld error(s) while testing write.\n", num_errors);
+ if (num_errors) {
+ return -1;
+ }
+
+ printf ("Testing sub-erase...\n");
+ bool success = sub_erase_test(device, SUB_ERASE_TEST_SAMPLES);
+ if (!success) {
+ printf("Discovered errors while testing sub-erase.\n");
+ return -1;
+ } else {
+ printf("No errors while testing sub-erase.\n");
+ }
+
+ return 0;
+}
diff --git a/src/bsp/lk/lib/bio/include/lib/bio.h b/src/bsp/lk/lib/bio/include/lib/bio.h
new file mode 100644
index 0000000..0162f0b
--- /dev/null
+++ b/src/bsp/lk/lib/bio/include/lib/bio.h
@@ -0,0 +1,155 @@
+/*
+ * 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.
+ */
+#pragma once
+
+#include <assert.h>
+#include <sys/types.h>
+#include <list.h>
+
+__BEGIN_CDECLS;
+
+#define BIO_FLAGS_NONE (0 << 0)
+#define BIO_FLAG_CACHE_ALIGNED_READS (1 << 0)
+#define BIO_FLAG_CACHE_ALIGNED_WRITES (1 << 1)
+
+typedef uint32_t bnum_t;
+
+typedef struct bio_erase_geometry_info {
+ off_t start; // start of the region in bytes.
+ off_t size;
+ size_t erase_size;
+ size_t erase_shift;
+} bio_erase_geometry_info_t;
+
+typedef struct bdev {
+ struct list_node node;
+ volatile int ref;
+
+ /* info about the block device */
+ char *name;
+ off_t total_size;
+
+ size_t block_size;
+ size_t block_shift;
+ bnum_t block_count;
+ char *unique_uuid;
+ char *label;
+ bool is_gpt;
+
+ size_t geometry_count;
+ const bio_erase_geometry_info_t* geometry;
+
+ uint8_t erase_byte;
+
+ uint32_t flags;
+
+ /* function pointers */
+ ssize_t (*read)(struct bdev *, void *buf, off_t offset, size_t len);
+ ssize_t (*read_block)(struct bdev *, void *buf, bnum_t block, uint count);
+ ssize_t (*write)(struct bdev *, const void *buf, off_t offset, size_t len);
+ ssize_t (*write_block)(struct bdev *, const void *buf, bnum_t block, uint count);
+ ssize_t (*erase)(struct bdev *, off_t offset, size_t len);
+ int (*ioctl)(struct bdev *, int request, void *argp);
+ void (*close)(struct bdev *);
+} bdev_t;
+
+/* user api */
+bdev_t *bio_open(const char *name);
+bdev_t *bio_open_by_label(const char *label);
+void bio_close(bdev_t *dev);
+ssize_t bio_read(bdev_t *dev, void *buf, off_t offset, size_t len);
+ssize_t bio_read_block(bdev_t *dev, void *buf, bnum_t block, uint count);
+ssize_t bio_write(bdev_t *dev, const void *buf, off_t offset, size_t len);
+ssize_t bio_write_block(bdev_t *dev, const void *buf, bnum_t block, uint count);
+ssize_t bio_erase(bdev_t *dev, off_t offset, size_t len);
+int bio_ioctl(bdev_t *dev, int request, void *argp);
+
+/* register a block device */
+void bio_register_device(bdev_t *dev);
+void bio_unregister_device(bdev_t *dev);
+
+/* used during bdev construction */
+void bio_initialize_bdev(bdev_t* dev,
+ const char* name,
+ size_t block_size,
+ bnum_t block_count,
+ size_t geometry_count,
+ const bio_erase_geometry_info_t* geometry,
+ const uint32_t flags);
+
+/* debug stuff */
+void bio_dump_devices(void);
+
+/* subdevice support */
+status_t bio_publish_subdevice(const char *parent_dev,
+ const char *subdev,
+ bnum_t startblock,
+ bnum_t block_count);
+
+/* memory based block device */
+int create_membdev(const char *name, void *ptr, size_t len);
+void *destroy_membdev(const char *name);
+
+/* helper routine to trim an offset + len to the device */
+size_t bio_trim_range(const bdev_t *dev, off_t offset, size_t len);
+
+/* helper routine to trim to a block range in the device */
+uint bio_trim_block_range(const bdev_t *dev, bnum_t block, uint count);
+
+/* utility routine */
+static inline bool bio_does_overlap(uint64_t start1, uint64_t len1,
+ uint64_t start2, uint64_t len2)
+{
+ uint64_t end1 = start1 + len1;
+ uint64_t end2 = start2 + len2;
+
+ DEBUG_ASSERT(end1 >= start1);
+ DEBUG_ASSERT(end2 >= start2);
+
+ return (((start1 >= start2) && (start1 < end2)) ||
+ ((start2 >= start1) && (start2 < end1)));
+}
+
+static inline bool bio_contains_range(uint64_t container_start, uint64_t container_len,
+ uint64_t contained_start, uint64_t contained_len)
+{
+ uint64_t container_end = container_start + container_len;
+ uint64_t contained_end = contained_start + contained_len;
+
+ DEBUG_ASSERT(container_end >= container_start);
+ DEBUG_ASSERT(contained_end >= contained_start);
+
+ return ((container_start <= contained_start) &&
+ (container_end >= contained_end));
+}
+
+/* generic bio ioctls */
+enum bio_ioctl_num {
+ BIO_IOCTL_NULL = 0,
+ BIO_IOCTL_GET_MEM_MAP, /* if supported, request a pointer to the memory map of the device */
+ BIO_IOCTL_PUT_MEM_MAP, /* if needed, return the pointer (to 'close' the map) */
+ BIO_IOCTL_GET_MAP_ADDR, /* if supported, request a pointer to the memory map without putting the device into linear mode */
+ BIO_IOCTL_CUSTOM_START = 0x100, /* for custom to add new enum */
+};
+
+__END_CDECLS;
diff --git a/src/bsp/lk/lib/bio/mem.c b/src/bsp/lk/lib/bio/mem.c
new file mode 100644
index 0000000..d8e73ff
--- /dev/null
+++ b/src/bsp/lk/lib/bio/mem.c
@@ -0,0 +1,126 @@
+/*
+ * 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 <debug.h>
+#include <trace.h>
+#include <string.h>
+#include <stdlib.h>
+#include <lib/bio.h>
+
+#define LOCAL_TRACE 0
+
+#define BLOCKSIZE 512
+
+typedef struct mem_bdev {
+ bdev_t dev; // base device
+
+ void *ptr;
+} mem_bdev_t;
+
+static ssize_t mem_bdev_read(bdev_t *bdev, void *buf, off_t offset, size_t len)
+{
+ mem_bdev_t *mem = (mem_bdev_t *)bdev;
+
+ LTRACEF("bdev %s, buf %p, offset %lld, len %zu\n", bdev->name, buf, offset, len);
+
+ memcpy(buf, (uint8_t *)mem->ptr + offset, len);
+
+ return len;
+}
+
+static ssize_t mem_bdev_read_block(struct bdev *bdev, void *buf, bnum_t block, uint count)
+{
+ mem_bdev_t *mem = (mem_bdev_t *)bdev;
+
+ LTRACEF("bdev %s, buf %p, block %u, count %u\n", bdev->name, buf, block, count);
+
+ memcpy(buf, (uint8_t *)mem->ptr + block * BLOCKSIZE, count * BLOCKSIZE);
+
+ return count * BLOCKSIZE;
+}
+
+static ssize_t mem_bdev_write(bdev_t *bdev, const void *buf, off_t offset, size_t len)
+{
+ mem_bdev_t *mem = (mem_bdev_t *)bdev;
+
+ LTRACEF("bdev %s, buf %p, offset %lld, len %zu\n", bdev->name, buf, offset, len);
+
+ memcpy((uint8_t *)mem->ptr + offset, buf, len);
+
+ return len;
+}
+
+static ssize_t mem_bdev_write_block(struct bdev *bdev, const void *buf, bnum_t block, uint count)
+{
+ mem_bdev_t *mem = (mem_bdev_t *)bdev;
+
+ LTRACEF("bdev %s, buf %p, block %u, count %u\n", bdev->name, buf, block, count);
+
+ memcpy((uint8_t *)mem->ptr + block * BLOCKSIZE, buf, count * BLOCKSIZE);
+
+ return count * BLOCKSIZE;
+}
+
+int create_membdev(const char *name, void *ptr, size_t len)
+{
+ mem_bdev_t *mem = malloc(sizeof(mem_bdev_t));
+
+ if (!mem)
+ return -1;
+
+ /* set up the base device */
+ bio_initialize_bdev(&mem->dev, name, BLOCKSIZE, len / BLOCKSIZE, 0, NULL,
+ BIO_FLAGS_NONE);
+
+ /* our bits */
+ mem->ptr = ptr;
+ mem->dev.read = mem_bdev_read;
+ mem->dev.read_block = mem_bdev_read_block;
+ mem->dev.write = mem_bdev_write;
+ mem->dev.write_block = mem_bdev_write_block;
+
+ /* register it */
+ bio_register_device(&mem->dev);
+
+ return 0;
+}
+
+void *destroy_membdev(const char *name)
+{
+ mem_bdev_t *mem;
+ void *memptr;
+
+ ASSERT(name != NULL);
+
+ mem = (mem_bdev_t *)bio_open(name);
+
+ if (!mem)
+ return NULL;
+
+ bio_unregister_device(&mem->dev);
+ bio_close(&mem->dev);
+
+ memptr = mem->ptr;
+ free(mem);
+
+ return memptr;
+}
diff --git a/src/bsp/lk/lib/bio/rules.mk b/src/bsp/lk/lib/bio/rules.mk
new file mode 100644
index 0000000..aafffee
--- /dev/null
+++ b/src/bsp/lk/lib/bio/rules.mk
@@ -0,0 +1,11 @@
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_SRCS += \
+ $(LOCAL_DIR)/bio.c \
+ $(LOCAL_DIR)/debug.c \
+ $(LOCAL_DIR)/mem.c \
+ $(LOCAL_DIR)/subdev.c
+
+include make/module.mk
diff --git a/src/bsp/lk/lib/bio/subdev.c b/src/bsp/lk/lib/bio/subdev.c
new file mode 100644
index 0000000..ddda5e1
--- /dev/null
+++ b/src/bsp/lk/lib/bio/subdev.c
@@ -0,0 +1,207 @@
+/*
+ * 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 <debug.h>
+#include <err.h>
+#include <trace.h>
+#include <stdlib.h>
+#include <lib/bio.h>
+
+#define LOCAL_TRACE 0
+
+typedef struct {
+ // inheirit the usual bits
+ bdev_t dev;
+
+ // we're a subdevice of this
+ bdev_t *parent;
+
+ // Storage for our erase geometry info. Subdevices are only permitted to
+ // have homogeneous erase geometry.
+ bio_erase_geometry_info_t geometry;
+
+ // we're this many blocks into it
+ bnum_t offset;
+} subdev_t;
+
+static ssize_t subdev_read(struct bdev *_dev, void *buf, off_t offset, size_t len)
+{
+ subdev_t *subdev = (subdev_t *)_dev;
+
+ return bio_read(subdev->parent, buf, offset + ((off_t)subdev->offset * subdev->dev.block_size), len);
+}
+
+static ssize_t subdev_read_block(struct bdev *_dev, void *buf, bnum_t block, uint count)
+{
+ subdev_t *subdev = (subdev_t *)_dev;
+
+ return bio_read_block(subdev->parent, buf, block + subdev->offset, count);
+}
+
+static ssize_t subdev_write(struct bdev *_dev, const void *buf, off_t offset, size_t len)
+{
+ subdev_t *subdev = (subdev_t *)_dev;
+
+ return bio_write(subdev->parent, buf, offset + ((off_t)subdev->offset * subdev->dev.block_size), len);
+}
+
+static ssize_t subdev_write_block(struct bdev *_dev, const void *buf, bnum_t block, uint count)
+{
+ subdev_t *subdev = (subdev_t *)_dev;
+
+ return bio_write_block(subdev->parent, buf, block + subdev->offset, count);
+}
+
+static ssize_t subdev_erase(struct bdev *_dev, off_t offset, size_t len)
+{
+ subdev_t *subdev = (subdev_t *)_dev;
+
+ return bio_erase(subdev->parent, offset + ((off_t)subdev->offset * subdev->dev.block_size), len);
+}
+
+static void subdev_close(struct bdev *_dev)
+{
+ subdev_t *subdev = (subdev_t *)_dev;
+
+ bio_close(subdev->parent);
+ subdev->parent = NULL;
+}
+
+static int subdev_ioctl(struct bdev *_dev, int request, void *argp)
+{
+ subdev_t *subdev = (subdev_t *)_dev;
+
+ return bio_ioctl(subdev->parent, request, argp);
+}
+
+#define BAIL(__err) do { err = __err; goto bailout; } while (0)
+status_t bio_publish_subdevice(const char *parent_dev,
+ const char *subdev,
+ bnum_t startblock,
+ bnum_t block_count)
+{
+ status_t err = NO_ERROR;
+ bdev_t *parent = NULL;
+ subdev_t *sub = NULL;
+ size_t geometry_count;
+ bio_erase_geometry_info_t* geometry;
+
+ LTRACEF("parent \"%s\", sub \"%s\", startblock %u, count %u\n", parent_dev, subdev, startblock, block_count);
+
+ // Make sure our parent exists
+ parent = bio_open(parent_dev);
+ if (!parent) {
+ LTRACEF("Failed to find parent \"%s\"\n", parent_dev);
+ BAIL(ERR_NOT_FOUND);
+ }
+
+ // Allocate our sub-device.
+ sub = malloc(sizeof(subdev_t));
+ if (!sub) {
+ LTRACEF("Failed to allocate subdevice\n");
+ BAIL(ERR_NO_MEMORY);
+ }
+
+ /* Make sure we're able to do this. If the device has a specified erase
+ * geometry, the specified sub-region must exist entirely with one of our
+ * parent's erase regions, and be aligned to an erase unit boundary.
+ * Otherwise, the specified region must simply exist within the block range
+ * of the device.
+ */
+ if (parent->geometry_count && parent->geometry) {
+ uint64_t byte_start = ((uint64_t)startblock << parent->block_shift);
+ uint64_t byte_size = ((uint64_t)block_count << parent->block_shift);
+ const bio_erase_geometry_info_t* geo = NULL;
+
+ LTRACEF("Searching geometry for region which contains @[0x%llx, 0x%llx)\n",
+ byte_start, byte_start + byte_size);
+
+ // Start by finding the erase region which completely contains the requested range.
+ for (size_t i = 0; i < parent->geometry_count; ++i) {
+ geo = parent->geometry + i;
+
+ LTRACEF("Checking geometry @[0x%llx, 0x%llx) erase size 0x%zx\n",
+ geo->start,
+ geo->start + geo->size,
+ geo->erase_size);
+
+ if (bio_contains_range(geo->start, geo->size, byte_start, byte_size))
+ break;
+ }
+
+ if (!geo) {
+ LTRACEF("No suitable erase region found\n");
+ BAIL(ERR_INVALID_ARGS);
+ }
+
+ // Now check out alignment.
+ uint64_t erase_mask = ((uint64_t)0x1 << geo->erase_shift) - 1;
+ if ((byte_start & erase_mask) || (byte_size & erase_mask)) {
+ LTRACEF("Requested region has improper alignment/length\n");
+ BAIL(ERR_INVALID_ARGS);
+ }
+
+ geometry_count = 1;
+ geometry = &sub->geometry;
+
+ geometry->start = 0;
+ geometry->size = byte_size;
+ geometry->erase_size = geo->erase_size;
+ geometry->erase_shift = geo->erase_shift;
+ } else {
+ bnum_t endblock = startblock + block_count;
+
+ if ((endblock < startblock) || (endblock > parent->block_count))
+ BAIL(ERR_INVALID_ARGS);
+
+ geometry_count = 0;
+ geometry = NULL;
+ }
+
+ bio_initialize_bdev(&sub->dev, subdev,
+ parent->block_size, block_count,
+ geometry_count, geometry, BIO_FLAGS_NONE);
+
+ sub->parent = parent;
+ sub->dev.erase_byte = parent->erase_byte;
+ sub->offset = startblock;
+
+ sub->dev.read = &subdev_read;
+ sub->dev.read_block = &subdev_read_block;
+ sub->dev.write = &subdev_write;
+ sub->dev.write_block = &subdev_write_block;
+ sub->dev.erase = &subdev_erase;
+ sub->dev.close = &subdev_close;
+ sub->dev.ioctl = &subdev_ioctl;
+
+ bio_register_device(&sub->dev);
+
+bailout:
+ if (err < 0) {
+ if (NULL != parent)
+ bio_close(parent);
+ free(sub);
+ }
+
+ return err;
+}
+#undef BAIL