Skip to contentSkip to conversation

Fixes for gapped scatters

PATCH New Sagi Grimberg 8 participants 29 messages Jul 15, 2015 – Sep 3, 2015

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

cover Fixes for gapped scatters

This small set addresses some issues with gapped scattered IOs. The motivation of this is to have the iser driver take advantage of it too.

Patch 1 prevent gapped SG_IO if the driver can't handle gaps. Patches 2,3 prevent gapped integrity payloads (for drivers that support data integrity).

Review is welcome.

Christoph Hellwig (1):
block: copy multi iovec user mappings if QUEUE_FLAG_SG_GAPS is set

Sagi Grimberg (2):
block: Refuse request/bio merges with gaps in the integrity payload
block: Refuse adding appending a gapped integrity page to a bio

 block/bio-integrity.c |  9 +++++++++
 block/blk-integrity.c | 32 +++++++++++++++++++++++++++-----
 block/blk-map.c       |  4 +++-
 3 files changed, 39 insertions(+), 6 deletions(-)
Signature · 1 line
1.8.4.3
#1 lore raw

PATCH 1/3 block: copy multi iovec user mappings if QUEUE_FLAG_SG_GAPS is set

From: Christoph Hellwig <[email protected]>

For drivers that don't support gaps in the SG lists handed to them we must not create bios from multiple iovecs as they can be (and usually are) discontiguous. This doesn't matter for any current user, but will help to allow iSER which can't handle gaps to use the QUEUE_FLAG_SG_GAPS flag instead of using driver-local bounce buffering.

Signed-off-by Christoph Hellwig <hch at lst.de>
Signed-off-by Sagi Grimberg <sagig at mellanox.com>
---
 block/blk-map.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)
block/blk-map.c +3 -1
index da310a1..86962eb 100644
@@ -85,7 +85,9 @@ int blk_rq_map_user_iov(struct request_queue *q, struct request *rq,
 			unaligned = 1;
 	}
 
-	if (unaligned || (q->dma_pad_mask & iter->count) || map_data)
+	if (unaligned || map_data ||
+	    (q->dma_pad_mask & iter->count) ||
+	    ((q->queue_flags & (1 << QUEUE_FLAG_SG_GAPS) && iter->nr_segs > 1)))
 		bio = bio_copy_user_iov(q, map_data, iter, gfp_mask);
 	else
 		bio = bio_map_user_iov(q, iter, gfp_mask);
Signature · 1 line
1.8.4.3
#2 lore raw

PATCH 1/3 block: copy multi iovec user mappings if QUEUE_FLAG_SG_GAPS is set

Quoted 5 lines · Sagi Grimberg

For drivers that don't support gaps in the SG lists handed to them we must not create bios from multiple iovecs as they can be (and usually are) discontiguous. This doesn't matter for any current user, but will help to allow iSER which can't handle gaps to use the QUEUE_FLAG_SG_GAPS flag instead of using driver-local bounce buffering.

Maybe SG_GAPS is a bit of a misnomer. There are cases we can directly map a user iov with multiple discontiguous vectors. At least for NVMe it'd work if each iov's base and length are on page boundaries, we don't need to do the indirect copy.

Like with this user example:
---
int fd = open("/dev/nvme0n1", O_RDONLY | O_DIRECT); struct iovec iov[2];
unsigned char *buffer;

  	posix_memalign((void **)&buffer, 0x4000, 0x1000);

  	iov[0].iov_base = &buffer[0];
  	iov[1].iov_base = &buffer[0x2000];
  	iov[0].iov_len = 0x1000;
  	iov[1].iov_len = 0x1000;

readv(fd, iov, 2);
--

maintainer
#3 lore raw

PATCH 1/3 block: copy multi iovec user mappings if QUEUE_FLAG_SG_GAPS is set

Quoted 15 lines · Keith Busch
Quoted 6 lines · Sagi Grimberg

For drivers that don't support gaps in the SG lists handed to them we must
not create bios from multiple iovecs as they can be (and usually are) discontiguous. This doesn't matter for any current user, but will help to allow iSER which can't handle gaps to use the QUEUE_FLAG_SG_GAPS flag instead of using driver-local bounce buffering.

Maybe SG_GAPS is a bit of a misnomer. There are cases we can directly map a user iov with multiple discontiguous vectors. At least for NVMe it'd work if each iov's base and length are on page boundaries, we don't need to do the indirect copy.

Like with this user example:
---

Quoted 12 lines
     int fd = open("/dev/nvme0n1", O_RDONLY | O_DIRECT);
     struct iovec iov[2];
      unsigned char *buffer;

      posix_memalign((void **)&buffer, 0x4000, 0x1000);

      iov[0].iov_base = &buffer[0];
      iov[1].iov_base = &buffer[0x2000];
      iov[0].iov_len = 0x1000;
      iov[1].iov_len = 0x1000;

     readv(fd, iov, 2);

SG_GAPS is a bit of a misnomer, but it's not easy to explain exactly what that logic is in a few short words! SG gaps was the closest I could come up with.

Signature · 1 line
Jens Axboe
#4 lore raw

PATCH 1/3 block: copy multi iovec user mappings if QUEUE_FLAG_SG_GAPS is set

Quoted 3 lines · Jens Axboe

SG_GAPS is a bit of a misnomer, but it's not easy to explain exactly what that logic is in a few short words! SG gaps was the closest I could come up with.

The sense is wrong -- surely it should be 'NOGAPS'?

maintainer
#5 lore raw

PATCH 1/3 block: copy multi iovec user mappings if QUEUE_FLAG_SG_GAPS is set

Quoted 6 lines · Matthew Wilcox
Quoted 3 lines · Jens Axboe

SG_GAPS is a bit of a misnomer, but it's not easy to explain exactly what that logic is in a few short words! SG gaps was the closest I could come up with.

The sense is wrong -- surely it should be 'NOGAPS'?

That might make it clearer :-)

Signature · 1 line
Jens Axboe
#6 lore raw

PATCH 1/3 block: copy multi iovec user mappings if QUEUE_FLAG_SG_GAPS is set

In reply to Jens Axboe
Quoted 3 lines · Jens Axboe

SG_GAPS is a bit of a misnomer, but it's not easy to explain exactly what that logic is in a few short words! SG gaps was the closest I could come up with.

SG_NO_OFFSETS_INTO_TRAILING_PAGES would desscribe it, but is a little too long..

#7 lore raw

PATCH 1/3 block: copy multi iovec user mappings if QUEUE_FLAG_SG_GAPS is set

In reply to Keith Busch
Quoted 11 lines · Keith Busch
Quoted 5 lines · Sagi Grimberg

For drivers that don't support gaps in the SG lists handed to them we must not create bios from multiple iovecs as they can be (and usually are) discontiguous. This doesn't matter for any current user, but will help to allow iSER which can't handle gaps to use the QUEUE_FLAG_SG_GAPS flag instead of using driver-local bounce buffering.

Maybe SG_GAPS is a bit of a misnomer. There are cases we can directly map a user iov with multiple discontiguous vectors. At least for NVMe it'd work if each iov's base and length are on page boundaries, we don't need to do the indirect copy.

Yes, right now the check is a little pessimistic. If we really care for this use case we can iterate over all iovecs and check if they would introduce offsets into a page.

Note that NVMe never uses blk_rq_map_user_iov with more than a single iov so it doesn't apply there, there concern here is iSER, but the same rules apply there as well.

#8 lore raw

PATCH 2/3 block: Refuse request/bio merges with gaps in the integrity payload

In reply to Sagi Grimberg

If a driver turned on QUEUE_FLAG_SG_GAPS, it means that it cannot handle gaps so we must not allow those in the integrity payload as well.

Signed-off-by Sagi Grimberg <sagig at mellanox.com>
---
 block/blk-integrity.c | 32 +++++++++++++++++++++++++++-----
 1 file changed, 27 insertions(+), 5 deletions(-)
block/blk-integrity.c +27 -5
index 79ffb48..bb18e0a 100644
@@ -189,20 +189,31 @@ EXPORT_SYMBOL(blk_integrity_compare);
 bool blk_integrity_merge_rq(struct request_queue *q, struct request *req,
 			    struct request *next)
 {
+	struct bio_integrity_payload *bip = bio_integrity(req->bio);
+	struct bio_integrity_payload *bip_next = bio_integrity(next->bio);
+
 	if (blk_integrity_rq(req) == 0 && blk_integrity_rq(next) == 0)
 		return true;
 
 	if (blk_integrity_rq(req) == 0 || blk_integrity_rq(next) == 0)
 		return false;
 
-	if (bio_integrity(req->bio)->bip_flags !=
-	    bio_integrity(next->bio)->bip_flags)
+	if (bip->bip_flags != bip_next->bip_flags)
 		return false;
 
 	if (req->nr_integrity_segments + next->nr_integrity_segments >
 	    q->limits.max_integrity_segments)
 		return false;
 
+	if (q->queue_flags & (1 << QUEUE_FLAG_SG_GAPS)) {
+		struct bio_vec *iv, *iv_next;
+
+		iv = &bip->bip_vec[bip->bip_vcnt];
+		iv_next = &bip_next->bip_vec[0];
+		if (bvec_gap_to_prev(iv, iv_next->bv_offset))
+			return false;
+	}
+
 	return true;
 }
 EXPORT_SYMBOL(blk_integrity_merge_rq);
@@ -212,16 +223,27 @@ bool blk_integrity_merge_bio(struct request_queue *q, struct request *req,
 {
 	int nr_integrity_segs;
 	struct bio *next = bio->bi_next;
+	struct bio_integrity_payload *bip = bio_integrity(req->bio);
+	struct bio_integrity_payload *bip_next = bio_integrity(bio);
 
-	if (blk_integrity_rq(req) == 0 && bio_integrity(bio) == NULL)
+	if (blk_integrity_rq(req) == 0 && bip_next == NULL)
 		return true;
 
-	if (blk_integrity_rq(req) == 0 || bio_integrity(bio) == NULL)
+	if (blk_integrity_rq(req) == 0 || bip_next == NULL)
 		return false;
 
-	if (bio_integrity(req->bio)->bip_flags != bio_integrity(bio)->bip_flags)
+	if (bip->bip_flags != bip_next->bip_flags)
 		return false;
 
+	if (q->queue_flags & (1 << QUEUE_FLAG_SG_GAPS)) {
+		struct bio_vec *iv, *iv_next;
+
+		iv = &bip->bip_vec[bip->bip_vcnt - 1];
+		iv_next = &bip_next->bip_vec[bip_next->bip_vcnt];
+		if (bvec_gap_to_prev(iv, iv_next->bv_offset))
+			return false;
+	}
+
 	bio->bi_next = NULL;
 	nr_integrity_segs = blk_rq_count_integrity_sg(q, bio);
 	bio->bi_next = next;
Signature · 1 line
1.8.4.3
#9 lore raw

PATCH 3/3 block: Refuse adding appending a gapped integrity page to a bio

In reply to Sagi Grimberg

This is only theoretical at the moment given that the only subsystems that generate integrity payloads are the block layer itself and the scsi target. But when we will expose integrity meta-data to user-space, we'll need to refuse appending a page with a gap when QUEUE_FLAG_SG_GAPS flag is on.

Signed-off-by Sagi Grimberg <sagig at mellanox.com>
---
 block/bio-integrity.c | 9 +++++++++
 1 file changed, 9 insertions(+)
block/bio-integrity.c +9 -0
index 5cbd5d9..a18edb8 100644
@@ -140,6 +140,15 @@ int bio_integrity_add_page(struct bio *bio, struct page *page,
 
 	iv = bip->bip_vec + bip->bip_vcnt;
 
+	if (bip->bip_vcnt) {
+		struct request_queue *q = bdev_get_queue(bio->bi_bdev);
+		struct bio_vec *iv_prev = &bip->bip_vec[bip->bip_vcnt - 1];
+
+		if (q->queue_flags & (1 << QUEUE_FLAG_SG_GAPS) &&
+		    bvec_gap_to_prev(iv_prev, offset))
+			return 0;
+	}
+
 	iv->bv_page = page;
 	iv->bv_len = len;
 	iv->bv_offset = offset;
Signature · 1 line
1.8.4.3
maintainer
#10 lore raw

PATCH 3/3 block: Refuse adding appending a gapped integrity page to a bio

Quoted 17 lines · Sagi Grimberg

This is only theoretical at the moment given that the only subsystems that generate integrity payloads are the block layer itself and the scsi target. But when we will expose integrity meta-data to user-space, we'll need to refuse appending a page with a gap when QUEUE_FLAG_SG_GAPS flag is on.

Signed-off-by Sagi Grimberg <sagig at mellanox.com>
---
  block/bio-integrity.c | 9 +++++++++
  1 file changed, 9 insertions(+)
block/bio-integrity.c +0 -0
index 5cbd5d9..a18edb8 100644
@@ -140,6 +140,15 @@ int bio_integrity_add_page(struct bio *bio, struct page *page,
 

block/bio-integrity.c int bio_integrity_add_page(struct bio *bio, struct page *page, · line 150

Quoted 10 lines
block/bio-integrity.c +8 -0
  	iv = bip->bip_vec + bip->bip_vcnt;
 
+	if (bip->bip_vcnt) {
+		struct request_queue *q = bdev_get_queue(bio->bi_bdev);
+		struct bio_vec *iv_prev = &bip->bip_vec[bip->bip_vcnt - 1];
+
+		if (q->queue_flags & (1 << QUEUE_FLAG_SG_GAPS) &&
+		    bvec_gap_to_prev(iv_prev, offset))
+			return 0;
+	}

This chunk of code largely ends up being duplicated in several places with your patches, might be a good idea to pull that into a helper function. Then you can add the comment in one place as well :-)

Signature · 1 line
Jens Axboe
#11 lore raw

PATCH 3/3 block: Refuse adding appending a gapped integrity page to a bio

Quoted 3 lines · Jens Axboe

This chunk of code largely ends up being duplicated in several places with your patches, might be a good idea to pull that into a helper function. Then you can add the comment in one place as well :-)

Basically all the bio_vec-related code in block-integrity is copy & paste of the I/O path. In the long run we'll need to refactor it by adding bio_vec iteration helpers that can pick different anchors from the bio. But for now I think I'd rather have this quick fix.

maintainer
#12 lore raw

PATCH 3/3 block: Refuse adding appending a gapped integrity page to a bio

Quoted 9 lines · Christoph Hellwig
Quoted 3 lines · Jens Axboe

This chunk of code largely ends up being duplicated in several places with your patches, might be a good idea to pull that into a helper function. Then you can add the comment in one place as well :-)

Basically all the bio_vec-related code in block-integrity is copy & paste of the I/O path. In the long run we'll need to refactor it by adding bio_vec iteration helpers that can pick different anchors from the bio. But for now I think I'd rather have this quick fix.

I wasn't asking for a refactor of the bio_vec integrity code (though that would be nice...), just that we don't add 3 more instances of 'does this gap with previous'.

Signature · 1 line
Jens Axboe
#13 lore raw

PATCH 3/3 block: Refuse adding appending a gapped integrity page to a bio

Quoted 8 lines · Jens Axboe
Quoted 7 lines · Christoph Hellwig
Quoted 5 lines · Jens Axboe

This chunk of code largely ends up being duplicated in several places with
your patches, might be a good idea to pull that into a helper function. Then
you can add the comment in one place as well :-)

Quoted 9 lines
Quoted 4 lines

Basically all the bio_vec-related code in block-integrity is copy & paste of the I/O path. In the long run we'll need to refactor it by adding bio_vec iteration helpers that can pick different anchors from the bio. But for now I think I'd rather have this quick fix.

I wasn't asking for a refactor of the bio_vec integrity code (though that would be nice...), just that we don't add 3 more instances of 'does this gap with previous'.

Hi Jens,

I'll centralize the checks. No problem.

Oddly enough I still see "gappy" IO when I'm running in the non-mq path. I suspect it's coming from the elevator code somehow...

I assume no one tried to run it on non-mq right?

Sagi.

#14 lore raw

PATCH 3/3 block: Refuse adding appending a gapped integrity page to a bio

Quoted 1 line · Sagi Grimberg

I'll centralize the checks. No problem.

Did you get a chance to respin these patches? It would be good to get them in this merge window.

#15 lore raw

PATCH 3/3 block: Refuse adding appending a gapped integrity page to a bio

Quoted 6 lines · Christoph Hellwig
Quoted 1 line · Sagi Grimberg

I'll centralize the checks. No problem.

Did you get a chance to respin these patches? It would be good to get them in this merge window.

Actually I didn't. I started to, but then I noticed that I was still seeing gaps when using cfq (e.g. non-mq code path), I assume that this was never tested?

Since I was busy with the iser-target iWARP support with Steve so I didn't get a chance to get back to it yet...

Sagi.

#16 lore raw

PATCH 3/3 block: Refuse adding appending a gapped integrity page to a bio

Quoted 3 lines · Sagi Grimberg

Actually I didn't. I started to, but then I noticed that I was still seeing gaps when using cfq (e.g. non-mq code path), I assume that this was never tested?

It probably wasn't. The only user so far is the NVMe driver which is blk-mq only.

Quoted 2 lines

Since I was busy with the iser-target iWARP support with Steve so I didn't get a chance to get back to it yet...

Ok.

#17 lore raw

PATCH 3/3 block: Refuse adding appending a gapped integrity page to a bio

Quoted 7 lines · Christoph Hellwig
Quoted 3 lines · Sagi Grimberg

Actually I didn't. I started to, but then I noticed that I was still seeing gaps when using cfq (e.g. non-mq code path), I assume that this was never tested?

It probably wasn't. The only user so far is the NVMe driver which is blk-mq only.

So I got back to have a look on this. I originally thought that this issue was specific to io schedulers, but I don't think it is anymore, its just easier to trigger with io schedulers.

It seems we are only protecting against gapped back merges (i.e. appending a gapped bio to a request biotail) but we are not protecting against front merges (i.e. adding a gapped bio to request as the bio head).

Imagine we have two bio_vec elements and the queue boundary is 0xfff:
req_bvec: offset=0xe00 length=0x200
bio_bvec: offset=0x0 length=0x200

bvec_gap_to_prev() will allow back merging {req_bvec, bio_bvec} as bio_vec->offset=0x0 and req_bvec->offset + req_bvec->length is aligned to the queue boundary, but the problem is we might do a front merge {bio_bvec, req_bvec} which gives us a gap.

I'm able to reproduce this with iser with 512B sequential reads (encourage gapped merges) but I wonder how this issue was missed in nvme (the code seem to allow front merges)?

Anyway, the patch below seems to solved the issue for me:

Comments?

--
commit 100383e208045368b4e3ac20e3fa46bdad966df2

Author: Sagi Grimberg <sagig at mellanox.com>
Date:   Wed Sep 2 01:31:39 2015 +0300

block: Protect against front merges with gaps

We seem to have missed gaps detection on front merges. Introduce req_gap_to_next call that is should detect a gap in a front merge.

Signed-off-by Sagi Grimberg <sagig at mellanox.com>
block/blk-merge.c +0 -0
index 09bf5cb..c95152a 100644
@@ -327,9 +327,20 @@ int ll_back_merge_fn(struct request_queue *q, 

struct request *req,

         return ll_new_hw_segment(q, req, bio);

}

+static int req_gap_to_next(struct request *req, struct bio *bio) +{
+ struct bio *next = req->bio;
+
+ return bvec_gap_to_prev(req->q, &bio->bi_io_vec[bio->bi_vcnt - 1],
+ next->bi_io_vec[0].bv_offset);
+}
+
int ll_front_merge_fn(struct request_queue *q, struct request *req,
struct bio *bio)
{
+ if (req_gap_to_next(req, bio))
+ return 0;
+

         if (blk_rq_sectors(req) + bio_sectors(bio) >
             blk_rq_get_max_sectors(req)) {
                 req->cmd_flags |= REQ_NOMERGE;

--

maintainer
#18 lore raw

PATCH 3/3 block: Refuse adding appending a gapped integrity page to a bio

Quoted 27 lines · Sagi Grimberg
Quoted 7 lines · Christoph Hellwig
Quoted 3 lines · Sagi Grimberg

Actually I didn't. I started to, but then I noticed that I was still seeing gaps when using cfq (e.g. non-mq code path), I assume that this was never tested?

It probably wasn't. The only user so far is the NVMe driver which is blk-mq only.

So I got back to have a look on this. I originally thought that this issue was specific to io schedulers, but I don't think it is anymore, its just easier to trigger with io schedulers.

It seems we are only protecting against gapped back merges (i.e. appending a gapped bio to a request biotail) but we are not protecting against front merges (i.e. adding a gapped bio to request as the bio head).

Imagine we have two bio_vec elements and the queue boundary is 0xfff:
req_bvec: offset=0xe00 length=0x200
bio_bvec: offset=0x0 length=0x200

bvec_gap_to_prev() will allow back merging {req_bvec, bio_bvec} as bio_vec->offset=0x0 and req_bvec->offset + req_bvec->length is aligned to the queue boundary, but the problem is we might do a front merge {bio_bvec, req_bvec} which gives us a gap.

Quoted 7 lines

I'm able to reproduce this with iser with 512B sequential reads (encourage gapped merges) but I wonder how this issue was missed in nvme (the code seem to allow front merges)?

Anyway, the patch below seems to solved the issue for me:

Comments?

Weird, I guess front merging was overlooked when the initial patch was added. Looks correct to me, and as far as I can see, we have now got all bases covered.

But there's room for some cleanup now, where we check is a bit of a mess. If we kill the check in blk_rq_merge_ok() and only do them in the front/back merge points (and the req-to-req case), then I think that would be an improvement. Does the below work for you?

block/blk-merge.c +34 -20
index 30a0d9f89017..d226bc5e3b8d 100644
@@ -309,9 +309,39 @@ no_merge:
 	return 0;
 }
 
+static bool queue_gap_check(struct request_queue *q, struct bio *bio)
+{
+	if (q->queue_flags & (1 << QUEUE_FLAG_SG_GAPS) && bio_has_data(bio))
+		return true;
+
+	return false;
+}
+
+static bool bio_gap_to_prev(struct bio *prev, struct bio *next)
+{
+	return bvec_gap_to_prev(&prev->bi_io_vec[prev->bi_vcnt - 1],
+					next->bi_io_vec[0].bv_offset);
+}
+
+static bool req_gap_to_prev(struct request *req, struct bio *bio)
+{
+	return bio_gap_to_prev(req->biotail, bio);
+}
+
+static bool req_gap_to_next(struct request *req, struct bio *bio)
+{
+	struct bio *next = req->bio;
+
+	return bvec_gap_to_prev(&bio->bi_io_vec[bio->bi_vcnt - 1],
+					next->bi_io_vec[0].bv_offset);
+}
+
 int ll_back_merge_fn(struct request_queue *q, struct request *req,
 		     struct bio *bio)
 {
+	if (queue_gap_check(q, bio) && req_gap_to_prev(req, bio))
+		return 0;
+
 	if (blk_rq_sectors(req) + bio_sectors(bio) >
 	    blk_rq_get_max_sectors(req)) {
 		req->cmd_flags |= REQ_NOMERGE;
@@ -330,6 +360,9 @@ int ll_back_merge_fn(struct request_queue *q, struct request *req,
 int ll_front_merge_fn(struct request_queue *q, struct request *req,
 		      struct bio *bio)
 {
+	if (queue_gap_check(q, bio) && req_gap_to_next(req, bio))
+		return 0;
+
 	if (blk_rq_sectors(req) + bio_sectors(bio) >
 	    blk_rq_get_max_sectors(req)) {
 		req->cmd_flags |= REQ_NOMERGE;
@@ -356,14 +389,6 @@ static bool req_no_special_merge(struct request *req)
 	return !q->mq_ops && req->special;
 }
 
-static int req_gap_to_prev(struct request *req, struct request *next)
-{
-	struct bio *prev = req->biotail;
-
-	return bvec_gap_to_prev(&prev->bi_io_vec[prev->bi_vcnt - 1],
-				next->bio->bi_io_vec[0].bv_offset);
-}
-
 static int ll_merge_requests_fn(struct request_queue *q, struct request *req,
 				struct request *next)
 {
@@ -379,7 +404,7 @@ static int ll_merge_requests_fn(struct request_queue *q, struct request *req,
 		return 0;
 
 	if (test_bit(QUEUE_FLAG_SG_GAPS, &q->queue_flags) &&
-	    req_gap_to_prev(req, next))
+	    req_gap_to_prev(req, next->bio))
 		return 0;
 
 	/*
@@ -564,8 +589,6 @@ int blk_attempt_req_merge(struct request_queue *q, struct request *rq,
 
 bool blk_rq_merge_ok(struct request *rq, struct bio *bio)
 {
-	struct request_queue *q = rq->q;
-
 	if (!rq_mergeable(rq) || !bio_mergeable(bio))
 		return false;
 
@@ -589,15 +612,6 @@ bool blk_rq_merge_ok(struct request *rq, struct bio *bio)
 	    !blk_write_same_mergeable(rq->bio, bio))
 		return false;
 
-	/* Only check gaps if the bio carries data */
-	if (q->queue_flags & (1 << QUEUE_FLAG_SG_GAPS) && bio_has_data(bio)) {
-		struct bio_vec *bprev;
-
-		bprev = &rq->biotail->bi_io_vec[rq->biotail->bi_vcnt - 1];
-		if (bvec_gap_to_prev(bprev, bio->bi_io_vec[0].bv_offset))
-			return false;
-	}
-
 	return true;
 }
 
Signature · 1 line
Jens Axboe
#19 lore raw

PATCH 3/3 block: Refuse adding appending a gapped integrity page to a bio

Quoted 36 lines · Jens Axboe
Quoted 27 lines · Sagi Grimberg
Quoted 7 lines · Christoph Hellwig
Quoted 3 lines · Sagi Grimberg

Actually I didn't. I started to, but then I noticed that I was still seeing gaps when using cfq (e.g. non-mq code path), I assume that this was never tested?

It probably wasn't. The only user so far is the NVMe driver which is blk-mq only.

So I got back to have a look on this. I originally thought that this issue was specific to io schedulers, but I don't think it is anymore, its just easier to trigger with io schedulers.

It seems we are only protecting against gapped back merges (i.e. appending a gapped bio to a request biotail) but we are not protecting against front merges (i.e. adding a gapped bio to request as the bio head).

Imagine we have two bio_vec elements and the queue boundary is 0xfff:
req_bvec: offset=0xe00 length=0x200
bio_bvec: offset=0x0 length=0x200

bvec_gap_to_prev() will allow back merging {req_bvec, bio_bvec} as bio_vec->offset=0x0 and req_bvec->offset + req_bvec->length is aligned to the queue boundary, but the problem is we might do a front merge {bio_bvec, req_bvec} which gives us a gap.

Quoted 7 lines

I'm able to reproduce this with iser with 512B sequential reads (encourage gapped merges) but I wonder how this issue was missed in nvme (the code seem to allow front merges)?

Anyway, the patch below seems to solved the issue for me:

Comments?

Quoted 8 lines

Weird, I guess front merging was overlooked when the initial patch was added. Looks correct to me, and as far as I can see, we have now got all bases covered.

But there's room for some cleanup now, where we check is a bit of a mess. If we kill the check in blk_rq_merge_ok() and only do them in the front/back merge points (and the req-to-req case), then I think that would be an improvement.

Yea, we do get to checking back merges even for front merges in this point...

Quoted 1 line

Does the below work for you?

It's not on top of Keith virt_boundary patch right? First glance looks ok though.

Quoted 111 lines
block/blk-merge.c +33 -0
index 30a0d9f89017..d226bc5e3b8d 100644
@@ -309,9 +309,39 @@ no_merge:
     return 0;
 }
 
+static bool queue_gap_check(struct request_queue *q, struct bio *bio)
+{
+    if (q->queue_flags & (1 << QUEUE_FLAG_SG_GAPS) && bio_has_data(bio))
+        return true;
+
+    return false;
+}
+
+static bool bio_gap_to_prev(struct bio *prev, struct bio *next)
+{
+    return bvec_gap_to_prev(&prev->bi_io_vec[prev->bi_vcnt - 1],
+                    next->bi_io_vec[0].bv_offset);
+}
+
+static bool req_gap_to_prev(struct request *req, struct bio *bio)
+{
+    return bio_gap_to_prev(req->biotail, bio);
+}
+
+static bool req_gap_to_next(struct request *req, struct bio *bio)
+{
+    struct bio *next = req->bio;
+
+    return bvec_gap_to_prev(&bio->bi_io_vec[bio->bi_vcnt - 1],
+                    next->bi_io_vec[0].bv_offset);
+}
+
 int ll_back_merge_fn(struct request_queue *q, struct request *req,
              struct bio *bio)
 {
+    if (queue_gap_check(q, bio) && req_gap_to_prev(req, bio))
+        return 0;
+
     if (blk_rq_sectors(req) + bio_sectors(bio) >
         blk_rq_get_max_sectors(req)) {
         req->cmd_flags |= REQ_NOMERGE;
@@ -330,6 +360,9 @@ int ll_back_merge_fn(struct request_queue *q, struct
 request *req,
 int ll_front_merge_fn(struct request_queue *q, struct request *req,
               struct bio *bio)
 {
+    if (queue_gap_check(q, bio) && req_gap_to_next(req, bio))
+        return 0;
+
     if (blk_rq_sectors(req) + bio_sectors(bio) >
         blk_rq_get_max_sectors(req)) {
         req->cmd_flags |= REQ_NOMERGE;
block/blk-merge.c +0 -8
@@ -356,14 +389,6 @@ static bool req_no_special_merge(struct request *req)
     return !q->mq_ops && req->special;
 }
 
-static int req_gap_to_prev(struct request *req, struct request *next)
-{
-    struct bio *prev = req->biotail;
-
-    return bvec_gap_to_prev(&prev->bi_io_vec[prev->bi_vcnt - 1],
-                next->bio->bi_io_vec[0].bv_offset);
-}
-

static int ll_merge_requests_fn(struct request_queue *q, struct request *req,
struct request *next)
{

block/blk-merge.c +1 -1
@@ -379,7 +404,7 @@ static int ll_merge_requests_fn(struct request_queue
 *q, struct request *req,
         return 0;
 
     if (test_bit(QUEUE_FLAG_SG_GAPS, &q->queue_flags) &&
-        req_gap_to_prev(req, next))
+        req_gap_to_prev(req, next->bio))
         return 0;
 
     /*
block/blk-merge.c +0 -11
@@ -564,8 +589,6 @@ int blk_attempt_req_merge(struct request_queue *q,
 struct request *rq,
 
 bool blk_rq_merge_ok(struct request *rq, struct bio *bio)
 {
-    struct request_queue *q = rq->q;
-
     if (!rq_mergeable(rq) || !bio_mergeable(bio))
         return false;
@@ -589,15 +612,6 @@ bool blk_rq_merge_ok(struct request *rq, struct bio
 *bio)
         !blk_write_same_mergeable(rq->bio, bio))
         return false;
 
-    /* Only check gaps if the bio carries data */
-    if (q->queue_flags & (1 << QUEUE_FLAG_SG_GAPS) && bio_has_data(bio)) {
-        struct bio_vec *bprev;
-
-        bprev = &rq->biotail->bi_io_vec[rq->biotail->bi_vcnt - 1];
-        if (bvec_gap_to_prev(bprev, bio->bi_io_vec[0].bv_offset))
-            return false;
-    }
-
     return true;
 }
maintainer
#20 lore raw

PATCH 3/3 block: Refuse adding appending a gapped integrity page to a bio

Quoted 41 lines · Sagi Grimberg
Quoted 40 lines · Jens Axboe
Quoted 27 lines · Sagi Grimberg
Quoted 7 lines · Christoph Hellwig
Quoted 3 lines · Sagi Grimberg

Actually I didn't. I started to, but then I noticed that I was still seeing gaps when using cfq (e.g. non-mq code path), I assume that this was never tested?

It probably wasn't. The only user so far is the NVMe driver which is blk-mq only.

So I got back to have a look on this. I originally thought that this issue was specific to io schedulers, but I don't think it is anymore, its just easier to trigger with io schedulers.

It seems we are only protecting against gapped back merges (i.e. appending a gapped bio to a request biotail) but we are not protecting against front merges (i.e. adding a gapped bio to request as the bio head).

Imagine we have two bio_vec elements and the queue boundary is 0xfff:
req_bvec: offset=0xe00 length=0x200
bio_bvec: offset=0x0 length=0x200

bvec_gap_to_prev() will allow back merging {req_bvec, bio_bvec} as bio_vec->offset=0x0 and req_bvec->offset + req_bvec->length is aligned to the queue boundary, but the problem is we might do a front merge {bio_bvec, req_bvec} which gives us a gap.

Quoted 7 lines

I'm able to reproduce this with iser with 512B sequential reads (encourage gapped merges) but I wonder how this issue was missed in nvme (the code seem to allow front merges)?

Anyway, the patch below seems to solved the issue for me:

Comments?

Weird, I guess front merging was overlooked when the initial patch was added. Looks correct to me, and as far as I can see, we have now got all bases covered.

Quoted 8 lines
Quoted 4 lines

But there's room for some cleanup now, where we check is a bit of a mess. If we kill the check in blk_rq_merge_ok() and only do them in the front/back merge points (and the req-to-req case), then I think that would be an improvement.

Yea, we do get to checking back merges even for front merges in this point...

Yup

Quoted 4 lines
Quoted 1 line

Does the below work for you?

It's not on top of Keith virt_boundary patch right? First glance looks ok though.

Looks like I was on the wrong branch, master. Let me update it for post that patch.

Signature · 1 line
Jens Axboe
maintainer
#21 lore raw

PATCH 3/3 block: Refuse adding appending a gapped integrity page to a bio

In reply to Sagi Grimberg
Quoted 4 lines · Sagi Grimberg
Quoted 1 line

Does the below work for you?

It's not on top of Keith virt_boundary patch right? First glance looks ok though.

Updated for that.

block/blk-merge.c +21 -12
index b2625271a572..5f35a05337b1 100644
@@ -436,9 +436,27 @@ no_merge:
 	return 0;
 }
 
+static bool req_gap_to_prev(struct request *req, struct bio *next)
+{
+	struct bio *prev = req->biotail;
+
+	return bvec_gap_to_prev(req->q, &prev->bi_io_vec[prev->bi_vcnt - 1],
+				next->bi_io_vec[0].bv_offset);
+}
+
+static bool req_gap_to_next(struct request *req, struct bio *bio)
+{
+	struct bio *next = req->bio;
+
+	return bvec_gap_to_prev(req->q, &bio->bi_io_vec[bio->bi_vcnt - 1],
+				next->bi_io_vec[0].bv_offset);
+}
+
 int ll_back_merge_fn(struct request_queue *q, struct request *req,
 		     struct bio *bio)
 {
+	if (bio_has_data(bio) && req_gap_to_prev(req, bio))
+		return 0;
 	if (blk_rq_sectors(req) + bio_sectors(bio) >
 	    blk_rq_get_max_sectors(req)) {
 		req->cmd_flags |= REQ_NOMERGE;
@@ -457,6 +475,9 @@ int ll_back_merge_fn(struct request_queue *q, struct request *req,
 int ll_front_merge_fn(struct request_queue *q, struct request *req,
 		      struct bio *bio)
 {
+
+	if (bio_has_data(bio) && req_gap_to_next(req, bio))
+		return 0;
 	if (blk_rq_sectors(req) + bio_sectors(bio) >
 	    blk_rq_get_max_sectors(req)) {
 		req->cmd_flags |= REQ_NOMERGE;
@@ -483,14 +504,6 @@ static bool req_no_special_merge(struct request *req)
 	return !q->mq_ops && req->special;
 }
 
-static int req_gap_to_prev(struct request *req, struct bio *next)
-{
-	struct bio *prev = req->biotail;
-
-	return bvec_gap_to_prev(req->q, &prev->bi_io_vec[prev->bi_vcnt - 1],
-			next->bi_io_vec[0].bv_offset);
-}
-
 static int ll_merge_requests_fn(struct request_queue *q, struct request *req,
 				struct request *next)
 {
@@ -713,10 +726,6 @@ bool blk_rq_merge_ok(struct request *rq, struct bio *bio)
 	    !blk_write_same_mergeable(rq->bio, bio))
 		return false;
 
-	/* Only check gaps if the bio carries data */
-	if (bio_has_data(bio) && req_gap_to_prev(rq, bio))
-		return false;
-
 	return true;
 }
 
Signature · 1 line
Jens Axboe
#22 lore raw

PATCH 3/3 block: Refuse adding appending a gapped integrity page to a bio

Quoted 8 lines · Jens Axboe
Quoted 4 lines · Sagi Grimberg
Quoted 1 line

Does the below work for you?

It's not on top of Keith virt_boundary patch right? First glance looks ok though.

Updated for that.

Thanks Jens,

I'll test it.

Quoted 34 lines
block/blk-merge.c +18 -0
index b2625271a572..5f35a05337b1 100644
@@ -436,9 +436,27 @@ no_merge:
  	return 0;
  }
 
+static bool req_gap_to_prev(struct request *req, struct bio *next)
+{
+	struct bio *prev = req->biotail;
+
+	return bvec_gap_to_prev(req->q, &prev->bi_io_vec[prev->bi_vcnt - 1],
+				next->bi_io_vec[0].bv_offset);
+}
+
+static bool req_gap_to_next(struct request *req, struct bio *bio)
+{
+	struct bio *next = req->bio;
+
+	return bvec_gap_to_prev(req->q, &bio->bi_io_vec[bio->bi_vcnt - 1],
+				next->bi_io_vec[0].bv_offset);
+}
+
  int ll_back_merge_fn(struct request_queue *q, struct request *req,
  		     struct bio *bio)
  {
+	if (bio_has_data(bio) && req_gap_to_prev(req, bio))
+		return 0;
  	if (blk_rq_sectors(req) + bio_sectors(bio) >
  	    blk_rq_get_max_sectors(req)) {
  		req->cmd_flags |= REQ_NOMERGE;

block/blk-merge.c int ll_back_merge_fn(struct request_queue *q, struct request *req, · line 480

Quoted 6 lines
block/blk-merge.c +3 -0
@@ -457,6 +475,9 @@ int ll_back_merge_fn(struct request_queue *q, struct request *req,
  int ll_front_merge_fn(struct request_queue *q, struct request *req,
  		      struct bio *bio)
  {
+
+	if (bio_has_data(bio) && req_gap_to_next(req, bio))
+		return 0;

I think it will be cleaner to git bio_has_data(bio) check into req_gap_to_[prev|next]

maintainer
#23 lore raw

PATCH 3/3 block: Refuse adding appending a gapped integrity page to a bio

Quoted 50 lines · Sagi Grimberg
Quoted 8 lines · Jens Axboe
Quoted 4 lines · Sagi Grimberg
Quoted 1 line

Does the below work for you?

It's not on top of Keith virt_boundary patch right? First glance looks ok though.

Updated for that.

Thanks Jens,

I'll test it.

Quoted 36 lines
block/blk-merge.c +18 -0
index b2625271a572..5f35a05337b1 100644
@@ -436,9 +436,27 @@ no_merge:
      return 0;
  }
 
+static bool req_gap_to_prev(struct request *req, struct bio *next)
+{
+    struct bio *prev = req->biotail;
+
+    return bvec_gap_to_prev(req->q, &prev->bi_io_vec[prev->bi_vcnt - 1],
+                next->bi_io_vec[0].bv_offset);
+}
+
+static bool req_gap_to_next(struct request *req, struct bio *bio)
+{
+    struct bio *next = req->bio;
+
+    return bvec_gap_to_prev(req->q, &bio->bi_io_vec[bio->bi_vcnt - 1],
+                next->bi_io_vec[0].bv_offset);
+}
+
  int ll_back_merge_fn(struct request_queue *q, struct request *req,
               struct bio *bio)
  {
+    if (bio_has_data(bio) && req_gap_to_prev(req, bio))
+        return 0;
      if (blk_rq_sectors(req) + bio_sectors(bio) >
          blk_rq_get_max_sectors(req)) {
          req->cmd_flags |= REQ_NOMERGE;
@@ -457,6 +475,9 @@ int ll_back_merge_fn(struct request_queue *q,
 struct request *req,
  int ll_front_merge_fn(struct request_queue *q, struct request *req,
Quoted 8 lines

block/blk-merge.c int ll_back_merge_fn(struct request_queue *q, struct request *req, · line 480

Quoted 5 lines
block/blk-merge.c +3 -0
                struct bio *bio)
  {
+
+    if (bio_has_data(bio) && req_gap_to_next(req, bio))
+        return 0;

I think it will be cleaner to git bio_has_data(bio) check into req_gap_to_[prev|next]

Yeah I agree, I'll make that change.

Signature · 1 line
Jens Axboe
maintainer
#24 lore raw

PATCH 3/3 block: Refuse adding appending a gapped integrity page to a bio

In reply to Sagi Grimberg
Quoted 7 lines · Sagi Grimberg
Quoted 6 lines · Jens Axboe
Quoted 4 lines · Sagi Grimberg
Quoted 1 line

Does the below work for you?

It's not on top of Keith virt_boundary patch right? First glance looks ok though.

Quoted 6 lines
Quoted 2 lines

Updated for that.

Thanks Jens,

I'll test it.

Cleaned up version, unified the gap checking and changed the names of the function to make it easier to understand. And then we only need to check bio_has_data() in bio_will_gap().

Functionally it should be identical to the previous one.

block/blk-merge.c +26 -13
index cce23ba1ae5f..bf9c45ddcd11 100644
@@ -435,9 +435,31 @@ no_merge:
 	return 0;
 }
 
+static bool bio_will_gap(struct request_queue *q, struct bio *prev,
+			 struct bio *next)
+{
+	if (!bio_has_data(prev))
+		return false;
+
+	return bvec_gap_to_prev(q, &prev->bi_io_vec[prev->bi_vcnt - 1],
+				next->bi_io_vec[0].bv_offset);
+}
+
+static bool req_gap_back_merge(struct request *req, struct bio *next)
+{
+	return bio_will_gap(req->q, req->biotail, next);
+}
+
+static bool req_gap_front_merge(struct request *req, struct bio *bio)
+{
+	return bio_will_gap(req->q, bio, req->bio);
+}
+
 int ll_back_merge_fn(struct request_queue *q, struct request *req,
 		     struct bio *bio)
 {
+	if (req_gap_back_merge(req, bio))
+		return 0;
 	if (blk_rq_sectors(req) + bio_sectors(bio) >
 	    blk_rq_get_max_sectors(req)) {
 		req->cmd_flags |= REQ_NOMERGE;
@@ -456,6 +478,9 @@ int ll_back_merge_fn(struct request_queue *q, struct request *req,
 int ll_front_merge_fn(struct request_queue *q, struct request *req,
 		      struct bio *bio)
 {
+
+	if (req_gap_front_merge(req, bio))
+		return 0;
 	if (blk_rq_sectors(req) + bio_sectors(bio) >
 	    blk_rq_get_max_sectors(req)) {
 		req->cmd_flags |= REQ_NOMERGE;
@@ -482,14 +507,6 @@ static bool req_no_special_merge(struct request *req)
 	return !q->mq_ops && req->special;
 }
 
-static int req_gap_to_prev(struct request *req, struct bio *next)
-{
-	struct bio *prev = req->biotail;
-
-	return bvec_gap_to_prev(req->q, &prev->bi_io_vec[prev->bi_vcnt - 1],
-			next->bi_io_vec[0].bv_offset);
-}
-
 static int ll_merge_requests_fn(struct request_queue *q, struct request *req,
 				struct request *next)
 {
@@ -504,7 +521,7 @@ static int ll_merge_requests_fn(struct request_queue *q, struct request *req,
 	if (req_no_special_merge(req) || req_no_special_merge(next))
 		return 0;
 
-	if (req_gap_to_prev(req, next->bio))
+	if (req_gap_back_merge(req, next->bio))
 		return 0;
 
 	/*
@@ -712,10 +729,6 @@ bool blk_rq_merge_ok(struct request *rq, struct bio *bio)
 	    !blk_write_same_mergeable(rq->bio, bio))
 		return false;
 
-	/* Only check gaps if the bio carries data */
-	if (bio_has_data(bio) && req_gap_to_prev(rq, bio))
-		return false;
-
 	return true;
 }
 
Signature · 1 line
Jens Axboe
#25 lore raw

PATCH 3/3 block: Refuse adding appending a gapped integrity page to a bio

Quoted 11 lines · Jens Axboe
Quoted 10 lines · Sagi Grimberg
Quoted 8 lines · Jens Axboe
Quoted 4 lines · Sagi Grimberg
Quoted 1 line

Does the below work for you?

It's not on top of Keith virt_boundary patch right? First glance looks ok though.

Updated for that.

Quoted 7 lines
Quoted 3 lines

Thanks Jens,

I'll test it.

Cleaned up version, unified the gap checking and changed the names of the function to make it easier to understand. And then we only need to check bio_has_data() in bio_will_gap().

:)

I was just going to say that maybe we should keep the check explicit..

I am going to fix this also for integrity payload [1], and there I need to check for blk_integrity_rq(req) explicitly because it doesn't really makes sense to call an integrity gap checker if you don't have integrity...

Also, lets move the gap helpers to be static inline in blkdev.h so I can put my integrity gap helpers there too.

Anyway, its just cosmetics...

Let me include your patch in a series I'm planning on sending soon enough as I don't see merges anymore so I guess the issue is gone now.

[1]:
commit 48679ce5f6ffe6827d40287b521ea139cdf95ff7

Author: Sagi Grimberg <sagig at mellanox.com>
Date:   Wed Jul 15 15:06:04 2015 +0300

block: Refuse request/bio merges with gaps in the integrity payload

If a driver sets the block queue virtual boundary, it means that it cannot handle gaps so we must not allow those in the integrity payload as well.

Signed-off-by Sagi Grimberg <sagig at mellanox.com>
block/blk-integrity.c +0 -0
index f548b64..eee1d74 100644
@@ -204,6 +204,9 @@ bool blk_integrity_merge_rq(struct request_queue *q, 

struct request *req,

             q->limits.max_integrity_segments)
                 return false;

+ if (integrity_req_gap_to_prev(req, next->bio))
+ return false;
+

         return true;

}
EXPORT_SYMBOL(blk_integrity_merge_rq);

block/blk-merge.c +0 -0
index e6b426f..1a3f105 100644
@@ -315,6 +315,10 @@ int ll_back_merge_fn(struct request_queue *q, 

struct request *req,

         if (bio_has_data(bio) && req_gap_to_prev(req, bio))
                 return 0;

+ if (blk_integrity_rq(req) &&
+ integrity_req_gap_to_prev(req, bio))
+ return 0;
+

         if (blk_rq_sectors(req) + bio_sectors(bio) >
             blk_rq_get_max_sectors(req)) {
             blk_rq_get_max_sectors(req)) {
                 req->cmd_flags |= REQ_NOMERGE;
@@ -336,6 +340,10 @@ int ll_front_merge_fn(struct request_queue *q, 
struct request *req,
         if (bio_has_data(bio) && req_gap_to_next(req, bio))
                 return 0;

+ if (blk_integrity_rq(req) &&
+ integrity_req_gap_to_next(req, bio))
+ return 0;
+

         if (blk_rq_sectors(req) + bio_sectors(bio) >
             blk_rq_get_max_sectors(req)) {
                 req->cmd_flags |= REQ_NOMERGE;
include/linux/blkdev.h +0 -0
index b1bb60a..ad5a21a 100644
@@ -1389,6 +1389,26 @@ static inline bool req_gap_to_next(struct request 

*req, struct bio *bio)

                                 next->bi_io_vec[0].bv_offset);

}

+static inline bool integrity_req_gap_to_prev(struct request *req,
+ struct bio *next)
+{
+ struct bio_integrity_payload *bip = bio_integrity(req->bio);
+ struct bio_integrity_payload *bip_next = bio_integrity(next);
+
+ return bvec_gap_to_prev(req->q, &bip->bip_vec[bip->bip_vcnt - 1],
+ bip_next->bip_vec[0].bv_offset);
+}
+
+static inline bool integrity_req_gap_to_next(struct request *req,
+ struct bio *bio)
+{
+ struct bio_integrity_payload *bip = bio_integrity(bio);
+ struct bio_integrity_payload *bip_next = bio_integrity(req->bio);
+
+ return bvec_gap_to_prev(req->q, &bip->bip_vec[bip->bip_vcnt - 1],
+ bip_next->bip_vec[0].bv_offset);
+}
struct work_struct;
int kblockd_schedule_work(struct work_struct *work);
int kblockd_schedule_delayed_work(struct delayed_work *dwork, unsigned
long delay);
--

maintainer
#26 lore raw

PATCH 3/3 block: Refuse adding appending a gapped integrity page to a bio

Quoted 16 lines · Sagi Grimberg
Quoted 15 lines · Jens Axboe
Quoted 13 lines · Sagi Grimberg
Quoted 8 lines · Jens Axboe
Quoted 4 lines · Sagi Grimberg
Quoted 1 line

Does the below work for you?

It's not on top of Keith virt_boundary patch right? First glance looks ok though.

Updated for that.

Thanks Jens,

I'll test it.

Quoted 8 lines
Quoted 3 lines

Cleaned up version, unified the gap checking and changed the names of the function to make it easier to understand. And then we only need to check bio_has_data() in bio_will_gap().

:)

I was just going to say that maybe we should keep the check explicit..

Hehe. I think it makes sense to check whether it carries data or not in the gap check, it's somewhat different than the integrity check.

Quoted 5 lines

I am going to fix this also for integrity payload [1], and there I need to check for blk_integrity_rq(req) explicitly because it doesn't really makes sense to call an integrity gap checker if you don't have integrity...

Quoted 8 lines

Also, lets move the gap helpers to be static inline in blkdev.h so I can put my integrity gap helpers there too.

Anyway, its just cosmetics...

Let me include your patch in a series I'm planning on sending soon enough as I don't see merges anymore so I guess the issue is gone now.

Updated patch below. Let me know when you have sufficient confidence in it working, and I will add your reviewed/tested/whatever-by and we can queue it up for this series.

Quoted 87 lines

commit 48679ce5f6ffe6827d40287b521ea139cdf95ff7

Author: Sagi Grimberg <sagig at mellanox.com>
Date:   Wed Jul 15 15:06:04 2015 +0300

block: Refuse request/bio merges with gaps in the integrity payload

If a driver sets the block queue virtual boundary, it means that it cannot handle gaps so we must not allow those in the integrity payload as well.

Signed-off-by Sagi Grimberg <sagig at mellanox.com>
block/blk-integrity.c +3 -0
index f548b64..eee1d74 100644
@@ -204,6 +204,9 @@ bool blk_integrity_merge_rq(struct request_queue *q,
 struct request *req,
             q->limits.max_integrity_segments)
                 return false;
 
+       if (integrity_req_gap_to_prev(req, next->bio))
+               return false;
+
         return true;
  }
  EXPORT_SYMBOL(blk_integrity_merge_rq);
block/blk-merge.c +4 -0
index e6b426f..1a3f105 100644
@@ -315,6 +315,10 @@ int ll_back_merge_fn(struct request_queue *q,
 struct request *req,
         if (bio_has_data(bio) && req_gap_to_prev(req, bio))
                 return 0;
 
+       if (blk_integrity_rq(req) &&
+           integrity_req_gap_to_prev(req, bio))
+               return 0;
+
         if (blk_rq_sectors(req) + bio_sectors(bio) >
             blk_rq_get_max_sectors(req)) {
             blk_rq_get_max_sectors(req)) {
                 req->cmd_flags |= REQ_NOMERGE;
block/blk-merge.c +4 -0
@@ -336,6 +340,10 @@ int ll_front_merge_fn(struct request_queue *q,
 struct request *req,
         if (bio_has_data(bio) && req_gap_to_next(req, bio))
                 return 0;
 
+       if (blk_integrity_rq(req) &&
+           integrity_req_gap_to_next(req, bio))
+               return 0;
+
         if (blk_rq_sectors(req) + bio_sectors(bio) >
             blk_rq_get_max_sectors(req)) {
                 req->cmd_flags |= REQ_NOMERGE;
include/linux/blkdev.h +19 -0
index b1bb60a..ad5a21a 100644
@@ -1389,6 +1389,26 @@ static inline bool req_gap_to_next(struct request
 *req, struct bio *bio)
                                 next->bi_io_vec[0].bv_offset);
  }
 
+static inline bool integrity_req_gap_to_prev(struct request *req,
+                                            struct bio *next)
+{
+       struct bio_integrity_payload *bip = bio_integrity(req->bio);
+       struct bio_integrity_payload *bip_next = bio_integrity(next);
+
+       return bvec_gap_to_prev(req->q, &bip->bip_vec[bip->bip_vcnt - 1],
+                               bip_next->bip_vec[0].bv_offset);
+}
+
+static inline bool integrity_req_gap_to_next(struct request *req,
+                                            struct bio *bio)
+{
+       struct bio_integrity_payload *bip = bio_integrity(bio);
+       struct bio_integrity_payload *bip_next = bio_integrity(req->bio);
+
+       return bvec_gap_to_prev(req->q, &bip->bip_vec[bip->bip_vcnt - 1],
+                               bip_next->bip_vec[0].bv_offset);
+}
  struct work_struct;
  int kblockd_schedule_work(struct work_struct *work);
  int kblockd_schedule_delayed_work(struct delayed_work *dwork, unsigned
Quoted 1 line

long delay);

Looks good to me.

block/blk-merge.c +6 -13
index cce23ba1ae5f..d9eddbc189f5 100644
@@ -438,6 +438,8 @@ no_merge:
 int ll_back_merge_fn(struct request_queue *q, struct request *req,
 		     struct bio *bio)
 {
+	if (req_gap_back_merge(req, bio))
+		return 0;
 	if (blk_rq_sectors(req) + bio_sectors(bio) >
 	    blk_rq_get_max_sectors(req)) {
 		req->cmd_flags |= REQ_NOMERGE;
@@ -456,6 +458,9 @@ int ll_back_merge_fn(struct request_queue *q, struct request *req,
 int ll_front_merge_fn(struct request_queue *q, struct request *req,
 		      struct bio *bio)
 {
+
+	if (req_gap_front_merge(req, bio))
+		return 0;
 	if (blk_rq_sectors(req) + bio_sectors(bio) >
 	    blk_rq_get_max_sectors(req)) {
 		req->cmd_flags |= REQ_NOMERGE;
@@ -482,14 +487,6 @@ static bool req_no_special_merge(struct request *req)
 	return !q->mq_ops && req->special;
 }
 
-static int req_gap_to_prev(struct request *req, struct bio *next)
-{
-	struct bio *prev = req->biotail;
-
-	return bvec_gap_to_prev(req->q, &prev->bi_io_vec[prev->bi_vcnt - 1],
-			next->bi_io_vec[0].bv_offset);
-}
-
 static int ll_merge_requests_fn(struct request_queue *q, struct request *req,
 				struct request *next)
 {
@@ -504,7 +501,7 @@ static int ll_merge_requests_fn(struct request_queue *q, struct request *req,
 	if (req_no_special_merge(req) || req_no_special_merge(next))
 		return 0;
 
-	if (req_gap_to_prev(req, next->bio))
+	if (req_gap_back_merge(req, next->bio))
 		return 0;
 
 	/*
@@ -712,10 +709,6 @@ bool blk_rq_merge_ok(struct request *rq, struct bio *bio)
 	    !blk_write_same_mergeable(rq->bio, bio))
 		return false;
 
-	/* Only check gaps if the bio carries data */
-	if (bio_has_data(bio) && req_gap_to_prev(rq, bio))
-		return false;
-
 	return true;
 }
 
include/linux/blkdev.h +20 -0
index a622f270f09e..0a362ed03c99 100644
@@ -1368,6 +1368,26 @@ static inline bool bvec_gap_to_prev(struct request_queue *q,
 		((bprv->bv_offset + bprv->bv_len) & queue_virt_boundary(q));
 }
 
+static inline bool bio_will_gap(struct request_queue *q, struct bio *prev,
+				struct bio *next)
+{
+	if (!bio_has_data(prev))
+		return false;
+
+	return bvec_gap_to_prev(q, &prev->bi_io_vec[prev->bi_vcnt - 1],
+				next->bi_io_vec[0].bv_offset);
+}
+
+static inline bool req_gap_back_merge(struct request *req, struct bio *next)
+{
+	return bio_will_gap(req->q, req->biotail, next);
+}
+
+static inline bool req_gap_front_merge(struct request *req, struct bio *bio)
+{
+	return bio_will_gap(req->q, bio, req->bio);
+}
+
 struct work_struct;
 int kblockd_schedule_work(struct work_struct *work);
 int kblockd_schedule_delayed_work(struct delayed_work *dwork, unsigned long delay);
Signature · 1 line
Jens Axboe
#27 lore raw

PATCH 3/3 block: Refuse adding appending a gapped integrity page to a bio

In reply to Christoph Hellwig
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line

"Christoph" == Christoph Hellwig <hch at infradead.org> writes:

Christoph> In the long run we'll need to refactor it by adding bio_vec Christoph> iteration helpers that can pick different anchors from the Christoph> bio.

Yep. Always wanted to avoid that duplication. Keith's work made it easier. I just haven't taken things one step further yet...

Signature · 1 line
Martin K. Petersen	Oracle Linux Engineering
#28 lore raw

cover Fixes for gapped scatters

In reply to Sagi Grimberg
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line

"Sagi" == Sagi Grimberg <sagig at mellanox.com> writes:

Sagi> This small set addresses some issues with gapped scattered Sagi> IOs. The motivation of this is to have the iser driver take Sagi> advantage of it too.

Sagi> Patch 1 prevent gapped SG_IO if the driver can't handle gaps. Sagi> Patches 2,3 prevent gapped integrity payloads (for drivers that Sagi> support data integrity).

Back in the prototyping days of DIX we dealt with several controllers that had various oddball scatterlist requirements for PI. The only workaround we found that performed acceptably was to grab a page and bounce the PI to that. In the end none of these controllers ended up shipping, and DIX explicitly requires a controller to be able to handle a separate scatterlist element per PI interval.

I know that for NVMe latency is king and so on. But I do question the sanity of having to issue a flurry 512+8 I/Os instead of quarter of a meg and a page of PI. You're at the whim of the kernel memory allocator and for a long time slab or slub was allocating backwards so you never ended up being able to merge PI scatterlist entries.

Also, if you were to go down this path you'd have to handle cases where a bio itself would need to get split. The PI for two contiguous 512-byte data blocks in a bio_vec entry could reside in separate integrity pages at offsets that would make them "gappy".

Signature · 1 line
Martin K. Petersen	Oracle Linux Engineering