[lib][bio] add simple block io layer
diff --git a/include/lib/bio.h b/include/lib/bio.h
new file mode 100644
index 0000000..a8d145c
--- /dev/null
+++ b/include/lib/bio.h
@@ -0,0 +1,81 @@
+/*
+ * 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.
+ */
+#ifndef __LIB_BIO_H
+#define __LIB_BIO_H
+
+#include <sys/types.h>
+#include <list.h>
+
+typedef uint32_t bnum_t;
+
+typedef struct bdev {
+	struct list_node node;
+	volatile int ref;
+
+	/* info about the block device */
+	char *name;
+	off_t size;
+	size_t block_size;
+	bnum_t block_count;
+
+	/* 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);
+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);
+
+/* intialize the block device layer */
+void bio_init(void);
+
+/* 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);
+
+/* debug stuff */
+void bio_dump_devices(void);
+
+/* subdevice support */
+status_t bio_publish_subdevice(const char *parent_dev, const char *subdev, bnum_t startblock, size_t len);
+
+/* memory based block device */
+int create_membdev(const char *name, void *ptr, size_t len);
+
+#endif
+
diff --git a/lib/bio/bio.c b/lib/bio/bio.c
new file mode 100644
index 0000000..3c35400
--- /dev/null
+++ b/lib/bio/bio.c
@@ -0,0 +1,445 @@
+/*
+ * 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);
+}
+
diff --git a/lib/bio/debug.c b/lib/bio/debug.c
new file mode 100644
index 0000000..90bfc11
--- /dev/null
+++ b/lib/bio/debug.c
@@ -0,0 +1,192 @@
+/*
+ * 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 <stdlib.h>
+#include <string.h>
+#include <lib/console.h>
+#include <lib/bio.h>
+#include <lib/partition.h>
+#include <platform.h>
+
+#if defined(WITH_LIB_CONSOLE)
+
+#if DEBUGLEVEL > 0
+static int cmd_bio(int argc, const cmd_args *argv);
+
+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) {
+		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 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);
+#if WITH_LIB_PARTITION
+		printf("%s partscan <device> [offset]\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) {
+			printf("not enough arguments:\n");
+			goto usage;
+		}
+
+		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;
+		}
+
+		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) {
+			printf("not enough arguments:\n");
+			goto usage;
+		}
+
+		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;
+		}
+
+		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, "erase")) {
+		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;
+		}
+
+		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) {
+			printf("not enough arguments:\n");
+			goto usage;
+		}
+
+		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 *)arg);
+		dprintf(INFO, "bio_ioctl returns %d\n", err);
+
+		bio_close(dev);
+
+		rc = err;
+	} else if (!strcmp(argv[1].str, "remove")) {
+		if (argc < 3) {
+			printf("not enough arguments:\n");
+			goto usage;
+		}
+
+		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);
+#if WITH_LIB_PARTITION
+	} else if (!strcmp(argv[1].str, "partscan")) {
+		if (argc < 3) {
+			printf("not enough arguments:\n");
+			goto usage;
+		}
+
+		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
+	} else {
+		printf("unrecognized subcommand\n");
+		goto usage;
+	}
+
+	return rc;
+}
+
+#endif
+
+#endif
+
diff --git a/lib/bio/mem.c b/lib/bio/mem.c
new file mode 100644
index 0000000..9ea2f4d
--- /dev/null
+++ b/lib/bio/mem.c
@@ -0,0 +1,101 @@
+/*
+ * 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 <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));
+
+	/* set up the base device */
+	bio_initialize_bdev(&mem->dev, name, BLOCKSIZE, len / BLOCKSIZE);
+
+	/* 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;
+}
+
diff --git a/lib/bio/rules.mk b/lib/bio/rules.mk
new file mode 100644
index 0000000..aaeb026
--- /dev/null
+++ b/lib/bio/rules.mk
@@ -0,0 +1,9 @@
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+MODULES +=
+
+OBJS += \
+	$(LOCAL_DIR)/bio.o \
+	$(LOCAL_DIR)/debug.o \
+	$(LOCAL_DIR)/mem.o \
+	$(LOCAL_DIR)/subdev.o 
diff --git a/lib/bio/subdev.c b/lib/bio/subdev.c
new file mode 100644
index 0000000..3512f02
--- /dev/null
+++ b/lib/bio/subdev.c
@@ -0,0 +1,112 @@
+/*
+ * 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 <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;
+
+	// 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 + 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 + 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 + 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;
+}
+
+status_t bio_publish_subdevice(const char *parent_dev, const char *subdev, bnum_t startblock, size_t len)
+{
+	LTRACEF("parent %s, sub %s, startblock %u, len %zd\n", parent_dev, subdev, startblock, len);
+
+	bdev_t *parent = bio_open(parent_dev);
+	if (!parent)
+		return -1;
+
+	/* make sure we're able to do this */
+	if (startblock + len > parent->block_count)
+		return -1;
+
+	subdev_t *sub = malloc(sizeof(subdev_t));
+	bio_initialize_bdev(&sub->dev, subdev, parent->block_size, len);
+
+	sub->parent = parent;
+	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;
+
+	bio_register_device(&sub->dev);
+
+	return 0;
+}
+