diff options
author | Russell King <rmk@dyn-67.arm.linux.org.uk> | 2006-01-03 23:38:44 +0100 |
---|---|---|
committer | Russell King <rmk+kernel@arm.linux.org.uk> | 2006-01-03 23:38:44 +0100 |
commit | a6f6c96b65d7f65a7a7bf5cbe874eda182a6b2cc (patch) | |
tree | 5f3bd4dc24866f2b0e593b1457b1f22ec641139b /drivers/mmc/mmc_block.c | |
parent | Linux v2.6.15 (diff) | |
download | linux-a6f6c96b65d7f65a7a7bf5cbe874eda182a6b2cc.tar.xz linux-a6f6c96b65d7f65a7a7bf5cbe874eda182a6b2cc.zip |
[MMC] Improve MMC card block size selection
Select a block size for IO based on the read and write block size
combinations, and whether the card supports partial block reads
and/or partial block writes.
If we are able to satisfy block reads but not block writes, mark
the device read only.
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
Diffstat (limited to 'drivers/mmc/mmc_block.c')
-rw-r--r-- | drivers/mmc/mmc_block.c | 175 |
1 files changed, 113 insertions, 62 deletions
diff --git a/drivers/mmc/mmc_block.c b/drivers/mmc/mmc_block.c index abcf19116d70..b9837cc5b9ac 100644 --- a/drivers/mmc/mmc_block.c +++ b/drivers/mmc/mmc_block.c @@ -54,6 +54,7 @@ struct mmc_blk_data { unsigned int usage; unsigned int block_bits; + unsigned int read_only; }; static DECLARE_MUTEX(open_lock); @@ -85,12 +86,6 @@ static void mmc_blk_put(struct mmc_blk_data *md) up(&open_lock); } -static inline int mmc_blk_readonly(struct mmc_card *card) -{ - return mmc_card_readonly(card) || - !(card->csd.cmdclass & CCC_BLOCK_WRITE); -} - static int mmc_blk_open(struct inode *inode, struct file *filp) { struct mmc_blk_data *md; @@ -102,8 +97,7 @@ static int mmc_blk_open(struct inode *inode, struct file *filp) check_disk_change(inode->i_bdev); ret = 0; - if ((filp->f_mode & FMODE_WRITE) && - mmc_blk_readonly(md->queue.card)) + if ((filp->f_mode & FMODE_WRITE) && md->read_only) ret = -EROFS; } @@ -299,6 +293,12 @@ static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req) static unsigned long dev_use[MMC_NUM_MINORS/(8*sizeof(unsigned long))]; +static inline int mmc_blk_readonly(struct mmc_card *card) +{ + return mmc_card_readonly(card) || + !(card->csd.cmdclass & CCC_BLOCK_WRITE); +} + static struct mmc_blk_data *mmc_blk_alloc(struct mmc_card *card) { struct mmc_blk_data *md; @@ -310,64 +310,121 @@ static struct mmc_blk_data *mmc_blk_alloc(struct mmc_card *card) __set_bit(devidx, dev_use); md = kmalloc(sizeof(struct mmc_blk_data), GFP_KERNEL); - if (md) { - memset(md, 0, sizeof(struct mmc_blk_data)); + if (!md) { + ret = -ENOMEM; + goto out; + } - md->disk = alloc_disk(1 << MMC_SHIFT); - if (md->disk == NULL) { - kfree(md); - md = ERR_PTR(-ENOMEM); - goto out; - } + memset(md, 0, sizeof(struct mmc_blk_data)); - spin_lock_init(&md->lock); - md->usage = 1; + /* + * Set the read-only status based on the supported commands + * and the write protect switch. + */ + md->read_only = mmc_blk_readonly(card); - ret = mmc_init_queue(&md->queue, card, &md->lock); - if (ret) { - put_disk(md->disk); - kfree(md); - md = ERR_PTR(ret); - goto out; + /* + * Figure out a workable block size. MMC cards have: + * - two block sizes, one for read and one for write. + * - may support partial reads and/or writes + * (allows block sizes smaller than specified) + */ + md->block_bits = card->csd.read_blkbits; + if (card->csd.write_blkbits != card->csd.read_blkbits) { + if (card->csd.write_blkbits < card->csd.read_blkbits && + card->csd.read_partial) { + /* + * write block size is smaller than read block + * size, but we support partial reads, so choose + * the smaller write block size. + */ + md->block_bits = card->csd.write_blkbits; + } else if (card->csd.write_blkbits > card->csd.read_blkbits && + card->csd.write_partial) { + /* + * read block size is smaller than write block + * size, but we support partial writes. Use read + * block size. + */ + } else { + /* + * We don't support this configuration for writes. + */ + printk(KERN_ERR "%s: unable to select block size for " + "writing (rb%u wb%u rp%u wp%u)\n", + md->disk->disk_name, + 1 << card->csd.read_blkbits, + 1 << card->csd.write_blkbits, + card->csd.read_partial, + card->csd.write_partial); + md->read_only = 1; } - md->queue.prep_fn = mmc_blk_prep_rq; - md->queue.issue_fn = mmc_blk_issue_rq; - md->queue.data = md; + } - md->disk->major = major; - md->disk->first_minor = devidx << MMC_SHIFT; - md->disk->fops = &mmc_bdops; - md->disk->private_data = md; - md->disk->queue = md->queue.queue; - md->disk->driverfs_dev = &card->dev; + /* + * Refuse to allow block sizes smaller than 512 bytes. + */ + if (md->block_bits < 9) { + printk(KERN_ERR "%s: unable to support block size %u\n", + mmc_card_id(card), 1 << md->block_bits); + ret = -EINVAL; + goto err_kfree; + } - /* - * As discussed on lkml, GENHD_FL_REMOVABLE should: - * - * - be set for removable media with permanent block devices - * - be unset for removable block devices with permanent media - * - * Since MMC block devices clearly fall under the second - * case, we do not set GENHD_FL_REMOVABLE. Userspace - * should use the block device creation/destruction hotplug - * messages to tell when the card is present. - */ + md->disk = alloc_disk(1 << MMC_SHIFT); + if (md->disk == NULL) { + ret = -ENOMEM; + goto err_kfree; + } - sprintf(md->disk->disk_name, "mmcblk%d", devidx); - sprintf(md->disk->devfs_name, "mmc/blk%d", devidx); + spin_lock_init(&md->lock); + md->usage = 1; - md->block_bits = card->csd.read_blkbits; + ret = mmc_init_queue(&md->queue, card, &md->lock); + if (ret) + goto err_putdisk; - blk_queue_hardsect_size(md->queue.queue, 1 << md->block_bits); + md->queue.prep_fn = mmc_blk_prep_rq; + md->queue.issue_fn = mmc_blk_issue_rq; + md->queue.data = md; - /* - * The CSD capacity field is in units of read_blkbits. - * set_capacity takes units of 512 bytes. - */ - set_capacity(md->disk, card->csd.capacity << (card->csd.read_blkbits - 9)); - } - out: + md->disk->major = major; + md->disk->first_minor = devidx << MMC_SHIFT; + md->disk->fops = &mmc_bdops; + md->disk->private_data = md; + md->disk->queue = md->queue.queue; + md->disk->driverfs_dev = &card->dev; + + /* + * As discussed on lkml, GENHD_FL_REMOVABLE should: + * + * - be set for removable media with permanent block devices + * - be unset for removable block devices with permanent media + * + * Since MMC block devices clearly fall under the second + * case, we do not set GENHD_FL_REMOVABLE. Userspace + * should use the block device creation/destruction hotplug + * messages to tell when the card is present. + */ + + sprintf(md->disk->disk_name, "mmcblk%d", devidx); + sprintf(md->disk->devfs_name, "mmc/blk%d", devidx); + + blk_queue_hardsect_size(md->queue.queue, 1 << md->block_bits); + + /* + * The CSD capacity field is in units of read_blkbits. + * set_capacity takes units of 512 bytes. + */ + set_capacity(md->disk, card->csd.capacity << (card->csd.read_blkbits - 9)); return md; + + err_putdisk: + put_disk(md->disk); + err_kfree: + kfree(md); + out: + return ERR_PTR(ret); } static int @@ -403,12 +460,6 @@ static int mmc_blk_probe(struct mmc_card *card) if (!(card->csd.cmdclass & CCC_BLOCK_READ)) return -ENODEV; - if (card->csd.read_blkbits < 9) { - printk(KERN_WARNING "%s: read blocksize too small (%u)\n", - mmc_card_id(card), 1 << card->csd.read_blkbits); - return -ENODEV; - } - md = mmc_blk_alloc(card); if (IS_ERR(md)) return PTR_ERR(md); @@ -419,7 +470,7 @@ static int mmc_blk_probe(struct mmc_card *card) printk(KERN_INFO "%s: %s %s %luKiB %s\n", md->disk->disk_name, mmc_card_id(card), mmc_card_name(card), - get_capacity(md->disk) >> 1, mmc_blk_readonly(card)?"(ro)":""); + get_capacity(md->disk) >> 1, md->read_only ? "(ro)" : ""); mmc_set_drvdata(card, md); add_disk(md->disk); |