From fa2a1f3425b14f44b7d6f07206e87f7d2bdcd9ca Mon Sep 17 00:00:00 2001 From: Caleb Sander Mateos Date: Sat, 27 Jun 2026 00:19:32 -0600 Subject: [PATCH 1/2] blk-integrity: add BLK_EXPECTED_REF_TAG_CAPABLE Add BLK_EXPECTED_REF_TAG_CAPABLE to enum blk_integrity_flags to allow a device to report support for specifying an expected initial ref tag in I/O. Make blk_integrity_remap() a no-op if the flag is set, as the ref tag seed used to generate/verify ref tags in the protection information can be passed as the expected initial ref tag. Ref tag remapping is necessary to merge bios with non-contiguous ref tag seeds, as it converts both bios' ref tags to/from absolute integrity interval numbers, which are contiguous. So don't merge bios to a BLK_EXPECTED_REF_TAG_CAPABLE device if the next bio's ref tag seed doesn't match the ref tag that would follow the end of the first bio. Signed-off-by: Caleb Sander Mateos --- block/blk-integrity.c | 18 ++++++++++++++++++ block/t10-pi.c | 3 ++- include/linux/blk-integrity.h | 2 ++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/block/blk-integrity.c b/block/blk-integrity.c index 964eebbee14d0..85ebe13f09120 100644 --- a/block/blk-integrity.c +++ b/block/blk-integrity.c @@ -141,6 +141,8 @@ bool blk_integrity_merge_rq(struct request_queue *q, struct request *req, struct request *next) { struct bio_integrity_payload *bip, *bip_next; + struct blk_integrity *bi; + u64 intervals; if (blk_integrity_rq(req) == 0 && blk_integrity_rq(next) == 0) return true; @@ -157,6 +159,13 @@ bool blk_integrity_merge_rq(struct request_queue *q, struct request *req, bip->app_tag != bip_next->app_tag) return false; + bi = blk_get_integrity(req->bio->bi_bdev->bd_disk); + intervals = blk_rq_bytes(req) >> bi->interval_exp; + if (bip->bip_flags & BIP_CHECK_REFTAG && + bi->flags & BLK_EXPECTED_REF_TAG_CAPABLE && + bip->bip_iter.bi_sector + intervals != bip_next->bip_iter.bi_sector) + return false; + if (req->nr_integrity_segments + next->nr_integrity_segments > q->limits.max_integrity_segments) return false; @@ -171,7 +180,9 @@ bool blk_integrity_merge_bio(struct request_queue *q, struct request *req, struct bio *bio) { struct bio_integrity_payload *bip, *bip_bio = bio_integrity(bio); + struct blk_integrity *bi; int nr_integrity_segs; + u64 intervals; if (blk_integrity_rq(req) == 0 && bip_bio == NULL) return true; @@ -187,6 +198,13 @@ bool blk_integrity_merge_bio(struct request_queue *q, struct request *req, bip->app_tag != bip_bio->app_tag) return false; + bi = blk_get_integrity(req->bio->bi_bdev->bd_disk); + intervals = blk_rq_bytes(req) >> bi->interval_exp; + if (bip->bip_flags & BIP_CHECK_REFTAG && + bi->flags & BLK_EXPECTED_REF_TAG_CAPABLE && + bip->bip_iter.bi_sector + intervals != bip_bio->bip_iter.bi_sector) + return false; + nr_integrity_segs = blk_rq_count_integrity_sg(q, bio); if (req->nr_integrity_segments + nr_integrity_segs > q->limits.max_integrity_segments) diff --git a/block/t10-pi.c b/block/t10-pi.c index a19b4e102a837..98b23345c91a8 100644 --- a/block/t10-pi.c +++ b/block/t10-pi.c @@ -546,7 +546,8 @@ static void blk_integrity_remap(struct request *rq, unsigned int nr_bytes, unsigned intervals = nr_bytes >> bi->interval_exp; struct bio *bio; - if (!(bi->flags & BLK_INTEGRITY_REF_TAG)) + if (!(bi->flags & BLK_INTEGRITY_REF_TAG) || + bi->flags & BLK_EXPECTED_REF_TAG_CAPABLE) return; __rq_for_each_bio(bio, rq) { diff --git a/include/linux/blk-integrity.h b/include/linux/blk-integrity.h index b1b530613c342..45686e1665453 100644 --- a/include/linux/blk-integrity.h +++ b/include/linux/blk-integrity.h @@ -15,6 +15,8 @@ enum blk_integrity_flags { BLK_INTEGRITY_REF_TAG = 1 << 3, BLK_INTEGRITY_STACKED = 1 << 4, BLK_SPLIT_INTERVAL_CAPABLE = 1 << 5, + /* Device I/O specifies expected initial ref tag independent of LBA */ + BLK_EXPECTED_REF_TAG_CAPABLE = 1 << 6, }; const char *blk_integrity_profile_name(struct blk_integrity *bi); From c4dce78c66206afe3cab11fcde303ceb4d63d66e Mon Sep 17 00:00:00 2001 From: Caleb Sander Mateos Date: Sat, 27 Jun 2026 00:19:33 -0600 Subject: [PATCH 2/2] nvme/core: advertise BLK_EXPECTED_REF_TAG_CAPABLE NVMe Read, Write, and Write Zeroes commands include an (E)ILBRT field to specify the expected initial reference tag for the controller to check against the ref tags in the protection information buffer. However, the NVMe driver currently always sets (E)ILBRT to the lower bits of the LBA. The block integrity layer generates/verifies the PI ref tags according to the bio's ref tag seed, so it must "remap" the ref tags, adjusting for the difference between the ref tag seed and the absolute integrity interval number (= LBA). If a request has an integrity payload, set (E)ILBRT to its ref tag seed so no ref tag remapping is required. Set BLK_EXPECTED_REF_TAG_CAPABLE in NVMe devices' enum blk_integrity_flags to skip the block integrity layer ref tag remapping. Signed-off-by: Caleb Sander Mateos --- drivers/nvme/host/core.c | 20 ++++++++++---------- include/linux/t10-pi.h | 5 ----- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index 453c1f0b2dd09..8202ca706c976 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -916,8 +916,7 @@ static void nvme_set_app_tag(struct request *req, struct nvme_command *cmnd) static void nvme_set_ref_tag(struct nvme_ns *ns, struct nvme_command *cmnd, struct request *req) { - u32 upper, lower; - u64 ref48; + u64 ref_tag; /* only type1 and type 2 PI formats have a reftag */ switch (ns->head->pi_type) { @@ -928,18 +927,19 @@ static void nvme_set_ref_tag(struct nvme_ns *ns, struct nvme_command *cmnd, return; } + ref_tag = full_pi_ref_tag(req); + if (blk_integrity_rq(req)) + ref_tag = bio_integrity(req->bio)->bip_iter.bi_sector; + /* both rw and write zeroes share the same reftag format */ switch (ns->head->guard_type) { case NVME_NVM_NS_16B_GUARD: - cmnd->rw.reftag = cpu_to_le32(t10_pi_ref_tag(req)); + cmnd->rw.reftag = cpu_to_le32(lower_32_bits(ref_tag)); break; case NVME_NVM_NS_64B_GUARD: - ref48 = ext_pi_ref_tag(req); - lower = lower_32_bits(ref48); - upper = upper_32_bits(ref48); - - cmnd->rw.reftag = cpu_to_le32(lower); - cmnd->rw.cdw3 = cpu_to_le32(upper); + ref_tag = lower_48_bits(ref_tag); + cmnd->rw.reftag = cpu_to_le32(lower_32_bits(ref_tag)); + cmnd->rw.cdw3 = cpu_to_le32(upper_32_bits(ref_tag)); break; default: break; @@ -1891,7 +1891,7 @@ static bool nvme_init_integrity(struct nvme_ns_head *head, break; } - bi->flags |= BLK_SPLIT_INTERVAL_CAPABLE; + bi->flags |= BLK_SPLIT_INTERVAL_CAPABLE | BLK_EXPECTED_REF_TAG_CAPABLE; bi->metadata_size = head->ms; if (bi->csum_type) { bi->pi_tuple_size = head->pi_size; diff --git a/include/linux/t10-pi.h b/include/linux/t10-pi.h index b6c2496866eab..5cf4859877f50 100644 --- a/include/linux/t10-pi.h +++ b/include/linux/t10-pi.h @@ -68,9 +68,4 @@ static inline u64 lower_48_bits(u64 n) return n & ((1ull << 48) - 1); } -static inline u64 ext_pi_ref_tag(struct request *rq) -{ - return lower_48_bits(full_pi_ref_tag(rq)); -} - #endif