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

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



From 15f8b6cda53848ced93a20554584dc860baab8b7 Mon Sep 17 00:00:00 2001
From: Adrian Hunter <adrian.hunter@xxxxxxxxx>
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 <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-mmc" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux USB Devel]     [Linux Media]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux