[PATCH 3/4] mmc_block: Add BLKDISCARD, BLKSECDISCARD and BLKDISCARDZEROES support

From: Adrian Hunter
Date: Thu Jun 03 2010 - 03:47:12 EST


Add implementations for ioctls BLKDISCARD and BLKSECDISCARD, and
flag the I/O queue if the SD/MMC card zeroes erased sectors.
N.B. SD/MMC cards set erased sectors either to ones or zeroes.

Signed-off-by: Adrian Hunter <adrian.hunter@xxxxxxxxx>
---
drivers/mmc/card/block.c | 152 ++++++++++++++++++++++++++++++++++++++++++++++
drivers/mmc/card/queue.c | 2 +
2 files changed, 154 insertions(+), 0 deletions(-)

diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c
index cb9fbc8..67f59bc 100644
--- a/drivers/mmc/card/block.c
+++ b/drivers/mmc/card/block.c
@@ -31,6 +31,7 @@
#include <linux/mutex.h>
#include <linux/scatterlist.h>
#include <linux/string_helpers.h>
+#include <linux/delay.h>

#include <linux/mmc/card.h>
#include <linux/mmc/host.h>
@@ -138,9 +139,160 @@ mmc_blk_getgeo(struct block_device *bdev, struct hd_geometry *geo)
return 0;
}

+static int mmc_blk_check_eod(struct block_device *bdev, unsigned int from,
+ unsigned int nr)
+{
+ unsigned int maxsector;
+
+ if (!nr)
+ return 0;
+
+ maxsector = bdev->bd_inode->i_size >> 9;
+ if (maxsector && (maxsector < nr || maxsector - nr < from))
+ return 1;
+
+ return 0;
+}
+
+static void mmc_blk_erase_throttle(struct mmc_blk_data *md)
+{
+ struct mmc_card *card = md->queue.card;
+ struct request_queue *q = md->queue.queue;
+ int i, ok;
+
+ for (i = 0; i < 40; i++) {
+ spin_lock_irq(q->queue_lock);
+ ok = elv_queue_empty(q);
+ spin_unlock_irq(q->queue_lock);
+ if (ok)
+ break;
+ mmc_release_host(card->host);
+ msleep(50);
+ mmc_claim_host(card->host);
+ }
+}
+
+static int mmc_blk_erase(struct mmc_blk_data *md, unsigned int from,
+ unsigned int nr)
+{
+ struct mmc_card *card = md->queue.card;
+ unsigned int n, arg;
+ int err;
+
+ if (!mmc_can_erase(card))
+ return -EOPNOTSUPP;
+
+ if (mmc_can_trim(card))
+ arg = MMC_TRIM_ARG;
+ else
+ arg = MMC_ERASE_ARG;
+
+ mmc_claim_host(card->host);
+ n = card->max_erase - (from % card->max_erase);
+ do {
+ /*
+ * Do not allow the BLKDISCARD ioctl to have priority over
+ * scheduled I/O.
+ */
+ mmc_blk_erase_throttle(md);
+ if (n > nr)
+ n = nr;
+ err = mmc_erase(card, from, n, arg);
+ if (err)
+ break;
+ from += n;
+ nr -= n;
+ n = card->max_erase;
+ } while (nr);
+ mmc_release_host(card->host);
+
+ return err;
+}
+
+static int mmc_blk_secure_erase(struct mmc_blk_data *md, unsigned int from,
+ unsigned int nr)
+{
+ struct mmc_card *card = md->queue.card;
+ unsigned int arg;
+ int err;
+
+ if (!mmc_can_secure_erase_trim(card))
+ return -EOPNOTSUPP;
+
+ if (mmc_can_trim(card) && !mmc_erase_group_aligned(card, from, nr))
+ arg = MMC_SECURE_TRIM1_ARG;
+ else
+ arg = MMC_SECURE_ERASE_ARG;
+
+ mmc_claim_host(card->host);
+ /*
+ * Secure erase can be very inefficient when used with any size
+ * significantly smaller than the size of the whole card, so do not
+ * break up the original request, but still wait for scheduled I/O
+ * in case the user is trying to securely erase one partition in
+ * small pieces while still using another partition on the same card.
+ */
+ mmc_blk_erase_throttle(md);
+ err = mmc_erase(card, from, nr, arg);
+ if (!err && arg == MMC_SECURE_TRIM1_ARG)
+ err = mmc_erase(card, from, nr, MMC_SECURE_TRIM2_ARG);
+ mmc_release_host(card->host);
+
+ return err;
+}
+
+static int mmc_blk_ioctl_discard(struct block_device *bdev, uint64_t start,
+ uint64_t len, int secure)
+{
+ struct mmc_blk_data *md = bdev->bd_disk->private_data;
+ unsigned int from, nr;
+
+ if ((start & 511) || (len & 511))
+ return -EINVAL;
+
+ start >>= 9;
+ len >>= 9;
+
+ if (start > UINT_MAX || len > UINT_MAX)
+ return -EINVAL;
+
+ from = start;
+ nr = len;
+
+ if (mmc_blk_check_eod(bdev, from, nr))
+ return -EINVAL;
+
+ if (bdev != bdev->bd_contains)
+ from += bdev->bd_part->start_sect;
+
+ if (secure)
+ return mmc_blk_secure_erase(md, from, nr);
+ else
+ return mmc_blk_erase(md, from, nr);
+}
+
+static int mmc_blk_ioctl(struct block_device *bdev, fmode_t mode,
+ unsigned cmd, unsigned long arg)
+{
+ uint64_t range[2];
+ int secure = 0;
+
+ switch (cmd) {
+ case BLKSECDISCARD:
+ secure = 1;
+ case BLKDISCARD:
+ if (copy_from_user(range, (void __user *)arg, sizeof(range)))
+ return -EFAULT;
+
+ return mmc_blk_ioctl_discard(bdev, range[0], range[1], secure);
+ }
+ return -ENOTTY;
+}
+
static const struct block_device_operations mmc_bdops = {
.open = mmc_blk_open,
.release = mmc_blk_release,
+ .ioctl = mmc_blk_ioctl,
.getgeo = mmc_blk_getgeo,
.owner = THIS_MODULE,
};
diff --git a/drivers/mmc/card/queue.c b/drivers/mmc/card/queue.c
index d6ded24..9b760d7 100644
--- a/drivers/mmc/card/queue.c
+++ b/drivers/mmc/card/queue.c
@@ -130,6 +130,8 @@ int mmc_init_queue(struct mmc_queue *mq, struct mmc_card *card, spinlock_t *lock
blk_queue_prep_rq(mq->queue, mmc_prep_request);
blk_queue_ordered(mq->queue, QUEUE_ORDERED_DRAIN, NULL);
queue_flag_set_unlocked(QUEUE_FLAG_NONROT, mq->queue);
+ if (card->erased_byte == 0)
+ mq->queue->limits.discard_zeroes_data = 1;

#ifdef CONFIG_MMC_BLOCK_BOUNCE
if (host->max_hw_segs == 1) {
--
1.6.3.3
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/