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@xxxxxxxxxxx> Cc: Jan Kara <jack@xxxxxxxx> Cc: Christoph Hellwig <hch@xxxxxx> Cc: Martin K. Petersen <martin.petersen@xxxxxxxxxx> Cc: Ming Lin <ming.l@xxxxxxxxxxxxxxx> Cc: Mike Snitzer <snitzer@xxxxxxxxxx> Cc: stable <stable@xxxxxxxxxxxxxxx> --- block/blk-lib.c | 74 ++++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 60 insertions(+), 14 deletions(-) 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 */ -- 2.1.4 -- To unsubscribe from this list: send the line "unsubscribe linux-block" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html