/* * block.c - simple block layer * * Copyright (c) 2011 Sascha Hauer , Pengutronix * * See file CREDITS for list of people who contributed to this * project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #define BLOCKSIZE(blk) (1 << blk->blockbits) #define WRBUFFER_LAST(blk) (blk->wrblock + blk->wrbufblocks - 1) #ifdef CONFIG_BLOCK_WRITE static int writebuffer_flush(struct block_device *blk) { if (!blk->wrbufblocks) return 0; 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); } if (blk->wrbufblocks == 0) { blk->wrblock = block; blk->wrbufblocks = 1; } 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; } #else static int writebuffer_flush(struct block_device *blk) { return 0; } #endif static void *block_get(struct block_device *blk, int block) { int ret; int num_blocks; if (block >= blk->num_blocks) return ERR_PTR(-EIO); /* first look into write buffer */ if (block >= blk->wrblock && block <= WRBUFFER_LAST(blk)) return blk->wrbuf + (block - blk->wrblock) * BLOCKSIZE(blk); /* 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); if (ret) return ERR_PTR(ret); blk->rdblock = block; blk->rdblockend = block + num_blocks - 1; return blk->rdbuf; } static ssize_t block_read(struct cdev *cdev, void *buf, size_t count, unsigned long offset, unsigned long flags) { struct block_device *blk = cdev->priv; unsigned long mask = BLOCKSIZE(blk) - 1; unsigned long block = offset >> blk->blockbits; size_t icount = count; int blocks; if (offset & mask) { size_t now = BLOCKSIZE(blk) - (offset & mask); void *iobuf = block_get(blk, block); now = min(count, now); if (IS_ERR(iobuf)) return PTR_ERR(iobuf); memcpy(buf, iobuf + (offset & mask), now); buf += now; count -= now; block++; } blocks = count >> blk->blockbits; while (blocks) { void *iobuf = block_get(blk, block); if (IS_ERR(iobuf)) return PTR_ERR(iobuf); memcpy(buf, iobuf, BLOCKSIZE(blk)); buf += BLOCKSIZE(blk); blocks--; block++; count -= BLOCKSIZE(blk); } if (count) { void *iobuf = block_get(blk, block); if (IS_ERR(iobuf)) return PTR_ERR(iobuf); memcpy(buf, iobuf, count); } return icount; } #ifdef CONFIG_BLOCK_WRITE static ssize_t block_write(struct cdev *cdev, const void *buf, size_t count, unsigned long offset, ulong flags) { struct block_device *blk = cdev->priv; unsigned long mask = BLOCKSIZE(blk) - 1; unsigned long block = offset >> blk->blockbits; size_t icount = count; int blocks; if (offset & mask) { size_t now = BLOCKSIZE(blk) - (offset & mask); void *iobuf = block_get(blk, block); now = min(count, now); if (IS_ERR(iobuf)) return PTR_ERR(iobuf); memcpy(iobuf + (offset & mask), buf, now); block_put(blk, iobuf, block); buf += now; count -= now; block++; } blocks = count >> blk->blockbits; while (blocks) { block_put(blk, buf, block); buf += BLOCKSIZE(blk); blocks--; block++; count -= BLOCKSIZE(blk); } if (count) { void *iobuf = block_get(blk, block); if (IS_ERR(iobuf)) return PTR_ERR(iobuf); memcpy(iobuf, buf, count); block_put(blk, iobuf, block); } return icount; } #endif static int block_close(struct cdev *cdev) { struct block_device *blk = cdev->priv; return writebuffer_flush(blk); } static int block_flush(struct cdev *cdev) { struct block_device *blk = cdev->priv; return writebuffer_flush(blk); } struct file_operations block_ops = { .read = block_read, #ifdef CONFIG_BLOCK_WRITE .write = block_write, #endif .close = block_close, .flush = block_flush, .lseek = dev_lseek_default, }; int blockdevice_register(struct block_device *blk) { size_t size = blk->num_blocks * BLOCKSIZE(blk); int ret; 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; ret = devfs_create(&blk->cdev); if (ret) return ret; return 0; } int blockdevice_unregister(struct block_device *blk) { return 0; }