| From 4921f7671814718e3e63189c5e692e67d55375fb Mon Sep 17 00:00:00 2001 |
| From: Ming Lei <ming.lei@redhat.com> |
| Date: Sun, 29 Dec 2019 10:32:30 +0800 |
| Subject: [PATCH] block: fix splitting segments on boundary masks |
| |
| commit 429120f3df2dba2bf3a4a19f4212a53ecefc7102 upstream. |
| |
| We ran into a problem with a mpt3sas based controller, where we would |
| see random (and hard to reproduce) file corruption). The issue seemed |
| specific to this controller, but wasn't specific to the file system. |
| After a lot of debugging, we find out that it's caused by segments |
| spanning a 4G memory boundary. This shouldn't happen, as the default |
| setting for segment boundary masks is 4G. |
| |
| Turns out there are two issues in get_max_segment_size(): |
| |
| 1) The default segment boundary mask is bypassed |
| |
| 2) The segment start address isn't taken into account when checking |
| segment boundary limit |
| |
| Fix these two issues by removing the bypass of the segment boundary |
| check even if the mask is set to the default value, and taking into |
| account the actual start address of the request when checking if a |
| segment needs splitting. |
| |
| Cc: stable@vger.kernel.org # v5.1+ |
| Reviewed-by: Chris Mason <clm@fb.com> |
| Tested-by: Chris Mason <clm@fb.com> |
| Fixes: dcebd755926b ("block: use bio_for_each_bvec() to compute multi-page bvec count") |
| Signed-off-by: Ming Lei <ming.lei@redhat.com> |
| |
| Dropped const on the page pointer, ppc page_to_phys() doesn't mark the |
| page as const... |
| |
| Signed-off-by: Jens Axboe <axboe@kernel.dk> |
| Signed-off-by: Paul Gortmaker <paul.gortmaker@windriver.com> |
| |
| diff --git a/block/blk-merge.c b/block/blk-merge.c |
| index 17713d7d98d5..849b718b5252 100644 |
| --- a/block/blk-merge.c |
| +++ b/block/blk-merge.c |
| @@ -144,16 +144,14 @@ static inline unsigned get_max_io_size(struct request_queue *q, |
| return sectors; |
| } |
| |
| -static unsigned get_max_segment_size(struct request_queue *q, |
| - unsigned offset) |
| +static inline unsigned get_max_segment_size(struct request_queue *q, |
| + struct page *start_page, |
| + unsigned long offset) |
| { |
| unsigned long mask = queue_segment_boundary(q); |
| |
| - /* default segment boundary mask means no boundary limit */ |
| - if (mask == BLK_SEG_BOUNDARY_MASK) |
| - return queue_max_segment_size(q); |
| - |
| - return min_t(unsigned long, mask - (mask & offset) + 1, |
| + offset = mask & (page_to_phys(start_page) + offset); |
| + return min_t(unsigned long, mask - offset + 1, |
| queue_max_segment_size(q)); |
| } |
| |
| @@ -173,7 +171,8 @@ static bool bvec_split_segs(struct request_queue *q, struct bio_vec *bv, |
| * current bvec has to be splitted as multiple segments. |
| */ |
| while (len && new_nsegs + *nsegs < max_segs) { |
| - seg_size = get_max_segment_size(q, bv->bv_offset + total_len); |
| + seg_size = get_max_segment_size(q, bv->bv_page, |
| + bv->bv_offset + total_len); |
| seg_size = min(seg_size, len); |
| |
| new_nsegs++; |
| @@ -375,7 +374,8 @@ static unsigned blk_bvec_map_sg(struct request_queue *q, |
| |
| while (nbytes > 0) { |
| unsigned offset = bvec->bv_offset + total; |
| - unsigned len = min(get_max_segment_size(q, offset), nbytes); |
| + unsigned len = min(get_max_segment_size(q, bvec->bv_page, |
| + offset), nbytes); |
| struct page *page = bvec->bv_page; |
| |
| /* |
| -- |
| 2.27.0 |
| |