diff mbox

[2/2] block: Fix blkdev_issue_discard()

Message ID 5670357C.2020100@sandisk.com (mailing list archive)
State New, archived
Headers show

Commit Message

Bart Van Assche Dec. 15, 2015, 3:45 p.m. UTC
If the start or the end of a discard range does not satisfy
the alignment requirements of a block driver, call
__blkdev_issue_zeroout() for the first and/or last part instead
of using REQ_DISCARD.

Signed-off-by: Bart Van Assche <bart.vanassche@sandisk.com>
Cc: Jan Kara <jack@suse.com>
Cc: Christoph Hellwig <hch@lst.de>
Cc: Martin K. Petersen <martin.petersen@oracle.com>
Cc: Ming Lin <ming.l@ssi.samsung.com>
Cc: Mike Snitzer <snitzer@redhat.com>
Cc: stable <stable@vger.kernel.org>
---
 block/blk-lib.c | 74 ++++++++++++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 60 insertions(+), 14 deletions(-)
diff mbox

Patch

diff --git a/block/blk-lib.c b/block/blk-lib.c
index 22e9f0f..6a91711 100644
--- a/block/blk-lib.c
+++ b/block/blk-lib.c
@@ -157,6 +157,42 @@  static int __blkdev_issue_zeroout(struct block_device *bdev, sector_t sector,
 	return ret;
 }
 
+/*
+ * Return the largest number that is less than or equal to @s and for which
+ * the remainder of the division by @granularity is @alignment.
+ */
+static sector_t round_sect_down(sector_t s, u32 granularity, u32 alignment)
+{
+	sector_t tmp = s, res = s;
+	u32 remainder;
+
+	remainder = sector_div(tmp, granularity);
+	if (remainder == alignment)
+		return res;
+	res -= remainder - alignment;
+	if (remainder < alignment)
+		res -= granularity;
+	return min(res, s);
+}
+
+/*
+ * Return the smallest number that is greater than or equal to @s and for which
+ * the remainder of the division by @granularity is @alignment.
+ */
+static sector_t round_sect_up(sector_t s, u32 granularity, u32 alignment)
+{
+	sector_t tmp = s, res = s;
+	u32 remainder;
+
+	remainder = sector_div(tmp, granularity);
+	if (remainder == alignment)
+		return res;
+	res += alignment - remainder;
+	if (alignment < remainder)
+		res += granularity;
+	return max(res, s);
+}
+
 /**
  * blkdev_issue_discard - queue a discard
  * @bdev:	blockdev to issue discard for
@@ -174,8 +210,9 @@  int blkdev_issue_discard(struct block_device *bdev, sector_t sector,
 	DECLARE_COMPLETION_ONSTACK(wait);
 	struct request_queue *q = bdev_get_queue(bdev);
 	int type = REQ_WRITE | REQ_DISCARD;
-	unsigned int granularity;
-	int alignment;
+	unsigned int granularity, req_sects;
+	sector_t end_sect;
+	int alignment, res;
 	struct bio_batch bb;
 	struct bio *bio;
 	int ret = 0;
@@ -202,10 +239,19 @@  int blkdev_issue_discard(struct block_device *bdev, sector_t sector,
 	bb.wait = &wait;
 
 	blk_start_plug(&plug);
+	end_sect = round_sect_up(sector, granularity, alignment);
+	if (sector < end_sect) {
+		req_sects = min(end_sect - sector, nr_sects);
+		res = __blkdev_issue_zeroout(bdev, sector, req_sects, gfp_mask);
+		if (res) {
+			bb.error = res;
+			req_sects = nr_sects;
+			end_sect = sector + nr_sects;
+		}
+		nr_sects -= req_sects;
+		sector = end_sect;
+	}
 	while (nr_sects) {
-		unsigned int req_sects;
-		sector_t end_sect, tmp;
-
 		bio = bio_alloc(gfp_mask, 1);
 		if (!bio) {
 			ret = -ENOMEM;
@@ -219,15 +265,12 @@  int blkdev_issue_discard(struct block_device *bdev, sector_t sector,
 		 * If splitting a request, and the next starting sector would be
 		 * misaligned, stop the discard at the previous aligned sector.
 		 */
-		end_sect = sector + req_sects;
-		tmp = end_sect;
-		if (req_sects < nr_sects &&
-		    sector_div(tmp, granularity) != alignment) {
-			end_sect = end_sect - alignment;
-			sector_div(end_sect, granularity);
-			end_sect = end_sect * granularity + alignment;
-			req_sects = end_sect - sector;
-		}
+		end_sect = round_sect_down(sector + req_sects, granularity,
+					   alignment);
+		if (end_sect <= sector)
+			break;
+
+		req_sects = end_sect - sector;
 
 		bio->bi_iter.bi_sector = sector;
 		bio->bi_end_io = bio_batch_end_io;
@@ -249,6 +292,9 @@  int blkdev_issue_discard(struct block_device *bdev, sector_t sector,
 		 */
 		cond_resched();
 	}
+	res = __blkdev_issue_zeroout(bdev, sector, nr_sects, gfp_mask);
+	if (res)
+		bb.error = res;
 	blk_finish_plug(&plug);
 
 	/* Wait for bios in-flight */