From 15f8b6cda53848ced93a20554584dc860baab8b7 Mon Sep 17 00:00:00 2001
From: Adrian Hunter <[email protected]>
Date: Thu, 3 Jun 2010 10:47:12 +0300
Subject: [PATCH 3/4] mmc_block: Add BLKDISCARD, BLKSECDISCARD and 
BLKDISCARDZEROES support

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 <[email protected]>
---
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-mmc" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to