From a6f6c96b65d7f65a7a7bf5cbe874eda182a6b2cc Mon Sep 17 00:00:00 2001 From: Russell King Date: Tue, 3 Jan 2006 22:38:44 +0000 Subject: [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 --- drivers/mmc/mmc.c | 10 +++ drivers/mmc/mmc_block.c | 175 +++++++++++++++++++++++++++++++----------------- 2 files changed, 123 insertions(+), 62 deletions(-) (limited to 'drivers/mmc') diff --git a/drivers/mmc/mmc.c b/drivers/mmc/mmc.c index eb41391e06e9..6696f71363b9 100644 --- a/drivers/mmc/mmc.c +++ b/drivers/mmc/mmc.c @@ -550,6 +550,11 @@ static void mmc_decode_csd(struct mmc_card *card) csd->capacity = (1 + m) << (e + 2); csd->read_blkbits = UNSTUFF_BITS(resp, 80, 4); + csd->read_partial = UNSTUFF_BITS(resp, 79, 1); + csd->write_misalign = UNSTUFF_BITS(resp, 78, 1); + csd->read_misalign = UNSTUFF_BITS(resp, 77, 1); + csd->write_blkbits = UNSTUFF_BITS(resp, 22, 4); + csd->write_partial = UNSTUFF_BITS(resp, 21, 1); } else { /* * We only understand CSD structure v1.1 and v1.2. @@ -579,6 +584,11 @@ static void mmc_decode_csd(struct mmc_card *card) csd->capacity = (1 + m) << (e + 2); csd->read_blkbits = UNSTUFF_BITS(resp, 80, 4); + csd->read_partial = UNSTUFF_BITS(resp, 79, 1); + csd->write_misalign = UNSTUFF_BITS(resp, 78, 1); + csd->read_misalign = UNSTUFF_BITS(resp, 77, 1); + csd->write_blkbits = UNSTUFF_BITS(resp, 22, 4); + csd->write_partial = UNSTUFF_BITS(resp, 21, 1); } } 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); -- cgit v1.2.3-59-g8ed1b From 63648fb5c0173614064e329503cc75c907dcb1a1 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Tue, 3 Jan 2006 22:56:56 +0000 Subject: [MMC] wbsd: make use of ARRAY_SIZE() macro Signed-off-by: Dmitry Torokhov Acked-by: Pierre Ossman --- drivers/mmc/wbsd.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) (limited to 'drivers/mmc') diff --git a/drivers/mmc/wbsd.c b/drivers/mmc/wbsd.c index c7eb7c269081..9df0ddeb3ff9 100644 --- a/drivers/mmc/wbsd.c +++ b/drivers/mmc/wbsd.c @@ -1456,13 +1456,11 @@ static int __devinit wbsd_scan(struct wbsd_host* host) * Iterate through all ports, all codes to * find hardware that is in our known list. */ - for (i = 0;i < sizeof(config_ports)/sizeof(int);i++) - { + for (i = 0; i < ARRAY_SIZE(config_ports); i++) { if (!request_region(config_ports[i], 2, DRIVER_NAME)) continue; - for (j = 0;j < sizeof(unlock_codes)/sizeof(int);j++) - { + for (j = 0; j < ARRAY_SIZE(unlock_codes); j++) { id = 0xFFFF; host->config = config_ports[i]; @@ -1478,8 +1476,7 @@ static int __devinit wbsd_scan(struct wbsd_host* host) wbsd_lock_config(host); - for (k = 0;k < sizeof(valid_ids)/sizeof(int);k++) - { + for (k = 0; k < ARRAY_SIZE(valid_ids); k++) { if (id == valid_ids[k]) { host->chip_id = id; -- cgit v1.2.3-59-g8ed1b From 21500bb32acd4c34b50e5d985712e29792c1b9ad Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Tue, 3 Jan 2006 22:57:35 +0000 Subject: [MMC] wbsd: convert to the new platfrom device interface platform_device_register_simple() is going away, switch to using platfrom_device_alloc() + platform_device_add(). Also make sure that wbsd_driver gets unregistered when wbsd_init fails. Signed-off-by: Dmitry Torokhov Acked-by: Pierre Ossman --- drivers/mmc/wbsd.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) (limited to 'drivers/mmc') diff --git a/drivers/mmc/wbsd.c b/drivers/mmc/wbsd.c index 9df0ddeb3ff9..4f13bd2ccf9a 100644 --- a/drivers/mmc/wbsd.c +++ b/drivers/mmc/wbsd.c @@ -2087,10 +2087,20 @@ static int __init wbsd_drv_init(void) if (result < 0) return result; - wbsd_device = platform_device_register_simple(DRIVER_NAME, -1, - NULL, 0); - if (IS_ERR(wbsd_device)) - return PTR_ERR(wbsd_device); + wbsd_device = platform_device_alloc(DRIVER_NAME, -1); + if (!wbsd_device) + { + platform_driver_unregister(&wbsd_driver); + return -ENOMEM; + } + + result = platform_device_add(wbsd_device); + if (result) + { + platform_device_put(wbsd_device); + platform_driver_unregister(&wbsd_driver); + return result; + } } return 0; -- cgit v1.2.3-59-g8ed1b From e9c091b47409255cefa1672041479d850b7b991a Mon Sep 17 00:00:00 2001 From: Russell King Date: Wed, 4 Jan 2006 16:24:05 +0000 Subject: [MMC] mmci: add data cache coherency Since MMCI currently uses PIO to read data, we have to take steps to ensure data cache coherency on aliasing CPU caches. Add the necessary flush_dcache_page() calls. Signed-off-by: Russell King --- drivers/mmc/mmci.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'drivers/mmc') diff --git a/drivers/mmc/mmci.c b/drivers/mmc/mmci.c index 166c9b0ad04e..6d161c70014a 100644 --- a/drivers/mmc/mmci.c +++ b/drivers/mmc/mmci.c @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -157,6 +158,13 @@ mmci_data_irq(struct mmci_host *host, struct mmc_data *data, else if (status & (MCI_TXUNDERRUN|MCI_RXOVERRUN)) data->error = MMC_ERR_FIFO; status |= MCI_DATAEND; + + /* + * We hit an error condition. Ensure that any data + * partially written to a page is properly coherent. + */ + if (host->sg_len && data->flags & MMC_DATA_READ) + flush_dcache_page(host->sg_ptr->page); } if (status & MCI_DATAEND) { mmci_stop_data(host); @@ -301,6 +309,13 @@ static irqreturn_t mmci_pio_irq(int irq, void *dev_id, struct pt_regs *regs) if (remain) break; + /* + * If we were reading, and we have completed this + * page, ensure that the data cache is coherent. + */ + if (status & MCI_RXACTIVE) + flush_dcache_page(host->sg_ptr->page); + if (!mmci_next_sg(host)) break; -- cgit v1.2.3-59-g8ed1b From f3e2628bed0d5a88ced8239b35f1534557f9631c Mon Sep 17 00:00:00 2001 From: Evgeniy Polyakov Date: Thu, 5 Jan 2006 10:31:23 +0000 Subject: [MMC] mmci: kunmap_atomic() unmaps virtual address, not page Signed-off-by: Evgeniy Polyakov Signed-off-by: Andrew Morton Signed-off-by: Russell King --- drivers/mmc/mmci.c | 2 +- drivers/mmc/mmci.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers/mmc') diff --git a/drivers/mmc/mmci.c b/drivers/mmc/mmci.c index 6d161c70014a..2b10a2d4ae09 100644 --- a/drivers/mmc/mmci.c +++ b/drivers/mmc/mmci.c @@ -300,7 +300,7 @@ static irqreturn_t mmci_pio_irq(int irq, void *dev_id, struct pt_regs *regs) /* * Unmap the buffer. */ - mmci_kunmap_atomic(host, &flags); + mmci_kunmap_atomic(host, buffer, &flags); host->sg_off += len; host->size -= len; diff --git a/drivers/mmc/mmci.h b/drivers/mmc/mmci.h index 4589bbd68192..6d7eadc9a678 100644 --- a/drivers/mmc/mmci.h +++ b/drivers/mmc/mmci.h @@ -172,8 +172,8 @@ static inline char *mmci_kmap_atomic(struct mmci_host *host, unsigned long *flag return kmap_atomic(sg->page, KM_BIO_SRC_IRQ) + sg->offset; } -static inline void mmci_kunmap_atomic(struct mmci_host *host, unsigned long *flags) +static inline void mmci_kunmap_atomic(struct mmci_host *host, void *buffer, unsigned long *flags) { - kunmap_atomic(host->sg_ptr->page, KM_BIO_SRC_IRQ); + kunmap_atomic(buffer, KM_BIO_SRC_IRQ); local_irq_restore(*flags); } -- cgit v1.2.3-59-g8ed1b