diff --git a/common/block.c b/common/block.c index 24377c6..4253fc4 100644 --- a/common/block.c +++ b/common/block.c @@ -21,89 +21,161 @@ */ #include #include +#include #include +#include #define BLOCKSIZE(blk) (1 << blk->blockbits) -#define WRBUFFER_LAST(blk) (blk->wrblock + blk->wrbufblocks - 1) +/* a chunk of contigous data */ +struct chunk { + void *data; /* data buffer */ + int block_start; /* first block in this chunk */ + int dirty; /* need to write back to device */ + int num; /* number of chunk, debugging only */ + struct list_head list; +}; -#ifdef CONFIG_BLOCK_WRITE +#define BUFSIZE (PAGE_SIZE * 16) + +/* + * Write all dirty chunks back to the device + */ static int writebuffer_flush(struct block_device *blk) { - if (!blk->wrbufblocks) - return 0; + struct chunk *chunk; - blk->ops->write(blk, blk->wrbuf, blk->wrblock, - blk->wrbufblocks); - - blk->wrbufblocks = 0; - - return 0; -} - -static int block_put(struct block_device *blk, const void *buf, int block) -{ - if (block >= blk->num_blocks) - return -EIO; - - if (block < blk->wrblock || block > blk->wrblock + blk->wrbufblocks) { - writebuffer_flush(blk); + list_for_each_entry(chunk, &blk->buffered_blocks, list) { + if (chunk->dirty) { + blk->ops->write(blk, chunk->data, chunk->block_start, blk->rdbufsize); + chunk->dirty = 0; + } } - if (blk->wrbufblocks == 0) { - blk->wrblock = block; - blk->wrbufblocks = 1; + return 0; +} + +/* + * get the chunk containing a given block. Will return NULL if the + * block is not cached, the chunk otherwise. + */ +static struct chunk *chunk_get_cached(struct block_device *blk, int block) +{ + struct chunk *chunk; + + list_for_each_entry(chunk, &blk->buffered_blocks, list) { + if (block >= chunk->block_start && + block < chunk->block_start + blk->rdbufsize) { + debug("%s: found %d in %d\n", __func__, block, chunk->num); + /* + * move most recently used entry to the head of the list + */ + list_move(&chunk->list, &blk->buffered_blocks); + return chunk; + } } - memcpy(blk->wrbuf + (block - blk->wrblock) * BLOCKSIZE(blk), - buf, BLOCKSIZE(blk)); - - if (block > WRBUFFER_LAST(blk)) - blk->wrbufblocks++; - - if (blk->wrbufblocks == blk->wrbufsize) - writebuffer_flush(blk); - - return 0; + return NULL; } -#else -static int writebuffer_flush(struct block_device *blk) +/* + * Get the data pointer for a given block. Will return NULL if + * the block is not cached, the data pointer otherwise. + */ +static void *block_get_cached(struct block_device *blk, int block) { + struct chunk *chunk; + + chunk = chunk_get_cached(blk, block); + if (!chunk) + return NULL; + + return chunk->data + (block - chunk->block_start) * BLOCKSIZE(blk); +} + +/* + * Get a data chunk, either from the idle list or if the idle list + * is empty, the least recently used is written back to disk and + * returned. + */ +static struct chunk *get_chunk(struct block_device *blk) +{ + struct chunk *chunk; + + if (list_empty(&blk->idle_blocks)) { + /* use last entry which is the most unused */ + chunk = list_last_entry(&blk->buffered_blocks, struct chunk, list); + if (chunk->dirty) { + size_t num_blocks = min(blk->rdbufsize, + blk->num_blocks - chunk->block_start); + blk->ops->write(blk, chunk->data, chunk->block_start, + num_blocks); + chunk->dirty = 0; + } + + list_del(&chunk->list); + } else { + chunk = list_first_entry(&blk->idle_blocks, struct chunk, list); + list_del(&chunk->list); + } + + return chunk; +} + +/* + * read a block into the cache. This assumes that the block is + * not cached already. By definition block_get_cached() for + * the same block will succeed after this call. + */ +static int block_cache(struct block_device *blk, int block) +{ + struct chunk *chunk; + size_t num_blocks; + int ret; + + chunk = get_chunk(blk); + chunk->block_start = block & ~blk->blkmask; + + debug("%s: %d to %d %s\n", __func__, chunk->block_start, + chunk->num); + + num_blocks = min(blk->rdbufsize, blk->num_blocks - chunk->block_start); + + ret = blk->ops->read(blk, chunk->data, chunk->block_start, num_blocks); + if (ret) { + list_add_tail(&chunk->list, &blk->idle_blocks); + return ret; + } + list_add(&chunk->list, &blk->buffered_blocks); + return 0; } -#endif +/* + * Get the data for a block, either from the cache or from + * the device. + */ static void *block_get(struct block_device *blk, int block) { + void *outdata; int ret; - int num_blocks; if (block >= blk->num_blocks) - return ERR_PTR(-EIO); + return NULL; - /* first look into write buffer */ - if (block >= blk->wrblock && block <= WRBUFFER_LAST(blk)) - return blk->wrbuf + (block - blk->wrblock) * BLOCKSIZE(blk); + outdata = block_get_cached(blk, block); + if (outdata) + return outdata; - /* then look into read buffer */ - if (block >= blk->rdblock && block <= blk->rdblockend) - return blk->rdbuf + (block - blk->rdblock) * BLOCKSIZE(blk); - - /* - * If none of the buffers above match read the block from - * the device - */ - num_blocks = min(blk->rdbufsize, blk->num_blocks - block); - - ret = blk->ops->read(blk, blk->rdbuf, block, num_blocks); + ret = block_cache(blk, block); if (ret) - return ERR_PTR(ret); + return NULL; - blk->rdblock = block; - blk->rdblockend = block + num_blocks - 1; + outdata = block_get_cached(blk, block); + if (!outdata) + BUG(); - return blk->rdbuf; + return outdata; } static ssize_t block_read(struct cdev *cdev, void *buf, size_t count, @@ -119,10 +191,10 @@ size_t now = BLOCKSIZE(blk) - (offset & mask); void *iobuf = block_get(blk, block); - now = min(count, now); + if (!iobuf) + return -EIO; - if (IS_ERR(iobuf)) - return PTR_ERR(iobuf); + now = min(count, now); memcpy(buf, iobuf + (offset & mask), now); buf += now; @@ -135,8 +207,8 @@ while (blocks) { void *iobuf = block_get(blk, block); - if (IS_ERR(iobuf)) - return PTR_ERR(iobuf); + if (!iobuf) + return -EIO; memcpy(buf, iobuf, BLOCKSIZE(blk)); buf += BLOCKSIZE(blk); @@ -148,8 +220,8 @@ if (count) { void *iobuf = block_get(blk, block); - if (IS_ERR(iobuf)) - return PTR_ERR(iobuf); + if (!iobuf) + return -EIO; memcpy(buf, iobuf, count); } @@ -158,6 +230,31 @@ } #ifdef CONFIG_BLOCK_WRITE + +/* + * Put data into a block. This only overwrites the data in the + * cache and marks the corresponding chunk as dirty. + */ +static int block_put(struct block_device *blk, const void *buf, int block) +{ + struct chunk *chunk; + void *data; + + if (block >= blk->num_blocks) + return -EINVAL; + + data = block_get(blk, block); + if (!data) + BUG(); + + memcpy(data, buf, 1 << blk->blockbits); + + chunk = chunk_get_cached(blk, block); + chunk->dirty = 1; + + return 0; +} + static ssize_t block_write(struct cdev *cdev, const void *buf, size_t count, unsigned long offset, ulong flags) { @@ -165,7 +262,7 @@ unsigned long mask = BLOCKSIZE(blk) - 1; unsigned long block = offset >> blk->blockbits; size_t icount = count; - int blocks; + int blocks, ret; if (offset & mask) { size_t now = BLOCKSIZE(blk) - (offset & mask); @@ -173,11 +270,14 @@ now = min(count, now); - if (IS_ERR(iobuf)) - return PTR_ERR(iobuf); + if (!iobuf) + return -EIO; memcpy(iobuf + (offset & mask), buf, now); - block_put(blk, iobuf, block); + ret = block_put(blk, iobuf, block); + if (ret) + return ret; + buf += now; count -= now; block++; @@ -186,7 +286,10 @@ blocks = count >> blk->blockbits; while (blocks) { - block_put(blk, buf, block); + ret = block_put(blk, buf, block); + if (ret) + return ret; + buf += BLOCKSIZE(blk); blocks--; block++; @@ -196,11 +299,13 @@ if (count) { void *iobuf = block_get(blk, block); - if (IS_ERR(iobuf)) - return PTR_ERR(iobuf); + if (!iobuf) + return -EIO; memcpy(iobuf, buf, count); - block_put(blk, iobuf, block); + ret = block_put(blk, iobuf, block); + if (ret) + return ret; } return icount; @@ -221,7 +326,7 @@ return writebuffer_flush(blk); } -struct file_operations block_ops = { +static struct file_operations block_ops = { .read = block_read, #ifdef CONFIG_BLOCK_WRITE .write = block_write, @@ -235,19 +340,27 @@ { size_t size = blk->num_blocks * BLOCKSIZE(blk); int ret; + int i; blk->cdev.size = size; blk->cdev.dev = blk->dev; blk->cdev.ops = &block_ops; blk->cdev.priv = blk; - blk->rdbufsize = PAGE_SIZE >> blk->blockbits; - blk->rdbuf = xmalloc(PAGE_SIZE); - blk->rdblock = 1; - blk->rdblockend = 0; - blk->wrbufsize = PAGE_SIZE >> blk->blockbits; - blk->wrbuf = xmalloc(PAGE_SIZE); - blk->wrblock = 0; - blk->wrbufblocks = 0; + blk->rdbufsize = BUFSIZE >> blk->blockbits; + + INIT_LIST_HEAD(&blk->buffered_blocks); + INIT_LIST_HEAD(&blk->idle_blocks); + blk->blkmask = blk->rdbufsize - 1; + + debug("%s: rdbufsize: %d blockbits: %d blkmask: 0x%08x\n", __func__, blk->rdbufsize, blk->blockbits, + blk->blkmask); + + for (i = 0; i < 8; i++) { + struct chunk *chunk = xzalloc(sizeof(*chunk)); + chunk->data = xmalloc(BUFSIZE); + chunk->num = i; + list_add_tail(&chunk->list, &blk->idle_blocks); + } ret = devfs_create(&blk->cdev); if (ret) @@ -258,6 +371,21 @@ int blockdevice_unregister(struct block_device *blk) { + struct chunk *chunk, *tmp; + + writebuffer_flush(blk); + + list_for_each_entry_safe(chunk, tmp, &blk->buffered_blocks, list) { + free(chunk->data); + free(chunk); + } + + list_for_each_entry_safe(chunk, tmp, &blk->idle_blocks, list) { + free(chunk->data); + free(chunk); + } + + devfs_remove(&blk->cdev); + return 0; } - diff --git a/include/block.h b/include/block.h index aaab4e3..cfa4cb9 100644 --- a/include/block.h +++ b/include/block.h @@ -8,21 +8,23 @@ struct block_device_ops { int (*read)(struct block_device *, void *buf, int block, int num_blocks); int (*write)(struct block_device *, const void *buf, int block, int num_blocks); + int (*read_start)(struct block_device *, void *buf, int block, int num_blocks); + int (*read_done)(struct block_device *); }; +struct chunk; + struct block_device { struct device_d *dev; struct block_device_ops *ops; int blockbits; int num_blocks; - void *rdbuf; /* read buffer */ int rdbufsize; - int rdblock; /* start block in read buffer */ - int rdblockend; /* end block in read buffer */ - void *wrbuf; /* write buffer */ - int wrblock; /* start block in write buffer */ - int wrbufblocks; /* number of blocks currently in write buffer */ - int wrbufsize; /* size of write buffer in blocks */ + int blkmask; + + struct list_head buffered_blocks; + struct list_head idle_blocks; + struct cdev cdev; };