bio_discard_limit() uses round_up() and round_down(), which require a power-of-two alignment. However, discard_granularity need not be a power of two. For example, dm-thin supports data block sizes that are arbitrary multiples of 64 KiB and advertises the block size as its granularity. With a 192 KiB granularity, discarding one complete block at offset 384 KiB is split into 128 KiB and 64 KiB bios. Neither covers a complete thin block, so dm-thin ignores both and the mapping remains allocated. Use div_u64_rem() to find the starting sector's remainder without modifying the sector. Round the maximum bio size down with rounddown() and subtract the remainder so that the next bio starts on a granularity boundary. This handles non-power-of-two granularities and avoids a small initial bio when a large discard starts at an unaligned sector. When discard is unsupported or disabled, discard_granularity can be zero. Return BIO_MAX_SECTORS in that case so the existing bio submission checks can handle unsupported discard requests without dividing by zero. Fixes: 9b15d109a6b2 ("block: improve discard bio alignment in __blkdev_issue_discard()") Reported-by: Ming-Hung Tsai Link: https://lore.kernel.org/r/CALjSBEvjbiGDVewuapWKhGDtaxZwcn4=22raUiBEOyj22rY7-g@mail.gmail.com Suggested-by: Christoph Hellwig Cc: stable@vger.kernel.org Signed-off-by: Hungyu Lin --- Changes in v3: - Use div_u64_rem() instead of sector_div(), as suggested by Christoph Hellwig, so the input sector is not modified. - Subtract the starting-sector remainder from the rounded maximum size instead of emitting a short initial bio, following Christoph's proposal. - Include explicitly and retain v2's zero-granularity guard. Changes in v2: - Handle zero discard_granularity before dividing, as pointed out by Sashiko. Leave unsupported discard handling to the bio submission path. v2: https://lore.kernel.org/r/20261026195131.46699-1-dennylin0707@gmail.com block/blk-lib.c | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/block/blk-lib.c b/block/blk-lib.c index 688bc67cbf7..e98aa483707 100644 --- a/block/blk-lib.c +++ b/block/blk-lib.c @@ -3,6 +3,7 @@ * Functions related to generic helpers functions */ #include +#include #include #include #include @@ -13,26 +14,25 @@ static sector_t bio_discard_limit(struct block_device *bdev, sector_t sector) { unsigned int discard_granularity = bdev_discard_granularity(bdev); - sector_t granularity_aligned_sector; + u32 unaligned_sectors; + + if (!discard_granularity) + return BIO_MAX_SECTORS; if (bdev_is_partition(bdev)) sector += bdev->bd_start_sect; - granularity_aligned_sector = - round_up(sector, discard_granularity >> SECTOR_SHIFT); - - /* - * Make sure subsequent bios start aligned to the discard granularity if - * it needs to be split. - */ - if (granularity_aligned_sector != sector) - return granularity_aligned_sector - sector; - /* - * Align the bio size to the discard granularity to make splitting the bio - * at discard granularity boundaries easier in the driver if needed. + * Align the bio size to the discard granularity to make splitting the + * bio at discard granularity boundaries easier in the driver if needed. + * + * If the current sector is not aligned, shorten the range so that the + * next one is aligned. */ - return round_down(BIO_MAX_SIZE, discard_granularity) >> SECTOR_SHIFT; + div_u64_rem(sector, discard_granularity >> SECTOR_SHIFT, + &unaligned_sectors); + return (rounddown(BIO_MAX_SIZE, discard_granularity) >> SECTOR_SHIFT) - + unaligned_sectors; } struct bio *blk_alloc_discard_bio(struct block_device *bdev, base-commit: 6628e7d4c6b25c8d18f1345e3a20d21829aae7e4 -- 2.50.1 (Apple Git-155)