Much work has recently gone into supporting block device integrity data (sometimes called "metadata") in Linux. Many NVMe devices these days support metadata transfers and/or automatic protection information generation and verification. However, ublk devices can't yet advertise integrity data capabilities. This patch series wires up support for integrity data in ublk. The ublk feature is referred to as "integrity" rather than "metadata" to match the block layer's name for it and to avoid confusion with the existing and unrelated UBLK_IO_F_META.
To advertise support for integrity data, a ublk server fills out the struct ublk_params's integrity field and sets UBLK_PARAM_TYPE_INTEGRITY. The struct ublk_param_integrity flags and csum_type fields use the existing LBMD_PI_* constants from the linux/fs.h UAPI header. The ublk driver fills out a corresponding struct blk_integrity.
When a request with integrity data is issued to the ublk device, the ublk driver sets UBLK_IO_F_INTEGRITY in struct ublksrv_io_desc's op_flags field. This is necessary for a ublk server for which bi_offload_capable() returns true to distinguish requests with integrity data from those without.
Integrity data transfers can currently only be performed via the ublk user copy mechanism. The overhead of zero-copy buffer registration makes it less appealing for the small transfers typical of integrity data. Additionally, neither io_uring NVMe passthru nor IORING_RW_ATTR_FLAG_PI currently allow an io_uring registered buffer for the integrity data. The ki_pos field of the struct kiocb passed to the user copy ->{read,write}_iter() callback gains a bit UBLKSRV_IO_INTEGRITY_FLAG for a ublk server to indicate whether to access the request's data or integrity data.
Not yet supported is an analogue for the IO_INTEGRITY_CHK_*/BIP_CHECK_* flags to ask the ublk server to verify the guard, reftag, and/or apptag of a request's protection information. The user copy mechanism currently forbids a ublk server from reading the data/integrity buffer of a read-direction request. We could potentially relax this restriction for integrity data on reads. Alternatively, the ublk driver could verify the requested fields as part of the user copy operation.
The first 2 commits harden blk_validate_integrity_limits() to reject nonsensical pi_offset and interval_exp integrity limits.
Caleb Sander Mateos (17): block: validate pi_offset integrity limit block: validate interval_exp integrity limit blk-integrity: take const pointer in blk_integrity_rq() ublk: move ublk flag check functions earlier ublk: set UBLK_IO_F_INTEGRITY in ublksrv_io_desc ublk: add ublk_copy_user_bvec() helper ublk: split out ublk_user_copy() helper ublk: inline ublk_check_and_get_req() into ublk_user_copy() ublk: move offset check out of __ublk_check_and_get_req() ublk: optimize ublk_user_copy() on daemon task selftests: ublk: add utility to get block device metadata size selftests: ublk: add kublk support for integrity params selftests: ublk: implement integrity user copy in kublk selftests: ublk: support non-O_DIRECT backing files selftests: ublk: add integrity data support to loop target selftests: ublk: add integrity params test selftests: ublk: add end-to-end integrity test
Stanley Zhang (3): ublk: add integrity UAPI ublk: support UBLK_PARAM_TYPE_INTEGRITY in device creation ublk: implement integrity user copy
block/blk-settings.c | 14 +- drivers/block/ublk_drv.c | 336 +++++++++++++------ include/linux/blk-integrity.h | 6 +- include/uapi/linux/ublk_cmd.h | 20 +- tools/testing/selftests/ublk/Makefile | 6 +- tools/testing/selftests/ublk/common.c | 4 +- tools/testing/selftests/ublk/fault_inject.c | 1 + tools/testing/selftests/ublk/file_backed.c | 61 +++- tools/testing/selftests/ublk/kublk.c | 85 ++++- tools/testing/selftests/ublk/kublk.h | 37 +- tools/testing/selftests/ublk/metadata_size.c | 37 ++ tools/testing/selftests/ublk/null.c | 1 + tools/testing/selftests/ublk/stripe.c | 6 +- tools/testing/selftests/ublk/test_common.sh | 10 + tools/testing/selftests/ublk/test_loop_08.sh | 111 ++++++ tools/testing/selftests/ublk/test_null_04.sh | 166 +++++++++ 16 files changed, 765 insertions(+), 136 deletions(-) create mode 100644 tools/testing/selftests/ublk/metadata_size.c create mode 100755 tools/testing/selftests/ublk/test_loop_08.sh create mode 100755 tools/testing/selftests/ublk/test_null_04.sh
From: Stanley Zhang stazhang@purestorage.com
Add a function ublk_copy_user_integrity() to copy integrity information between a request and a user iov_iter. This mirrors the existing ublk_copy_user_pages() but operates on request integrity data instead of regular data. Check UBLKSRV_IO_INTEGRITY_FLAG in iocb->ki_pos in ublk_user_copy() to choose between copying data or integrity data.
Signed-off-by: Stanley Zhang stazhang@purestorage.com [csander: change offset units from data bytes to integrity data bytes, test UBLKSRV_IO_INTEGRITY_FLAG after subtracting UBLKSRV_IO_BUF_OFFSET, fix CONFIG_BLK_DEV_INTEGRITY=n build, rebase on ublk user copy refactor] Signed-off-by: Caleb Sander Mateos csander@purestorage.com --- drivers/block/ublk_drv.c | 43 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-)
diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c index 7fa0a9f0bfae..042df4de9253 100644 --- a/drivers/block/ublk_drv.c +++ b/drivers/block/ublk_drv.c @@ -606,10 +606,15 @@ static inline unsigned ublk_pos_to_tag(loff_t pos) { return ((pos - UBLKSRV_IO_BUF_OFFSET) >> UBLK_TAG_OFF) & UBLK_TAG_BITS_MASK; }
+static inline bool ublk_pos_is_integrity(loff_t pos) +{ + return !!((pos - UBLKSRV_IO_BUF_OFFSET) & UBLKSRV_IO_INTEGRITY_FLAG); +} + static void ublk_dev_param_basic_apply(struct ublk_device *ub) { const struct ublk_param_basic *p = &ub->params.basic;
if (p->attrs & UBLK_ATTR_READ_ONLY) @@ -1034,10 +1039,31 @@ static size_t ublk_copy_user_pages(const struct request *req, break; } return done; }
+static size_t ublk_copy_user_integrity(const struct request *req, + unsigned offset, struct iov_iter *uiter, int dir) +{ + size_t done = 0; +#ifdef CONFIG_BLK_DEV_INTEGRITY + struct bio *bio = req->bio; + struct bvec_iter iter; + struct bio_vec iv; + + if (!blk_integrity_rq(req)) + return 0; + + bio_for_each_integrity_vec(iv, bio, iter) { + if (!ublk_copy_user_bvec(iv, &offset, uiter, dir, &done)) + break; + } +#endif + + return done; +} + static inline bool ublk_need_map_req(const struct request *req) { return ublk_rq_has_data(req) && req_op(req) == REQ_OP_WRITE; }
@@ -2616,10 +2642,12 @@ ublk_user_copy(struct kiocb *iocb, struct iov_iter *iter, int dir) { struct ublk_device *ub = iocb->ki_filp->private_data; struct ublk_queue *ubq; struct request *req; struct ublk_io *io; + unsigned data_len; + bool is_integrity; size_t buf_off; u16 tag, q_id; ssize_t ret;
if (!user_backed_iter(iter)) @@ -2629,10 +2657,11 @@ ublk_user_copy(struct kiocb *iocb, struct iov_iter *iter, int dir) return -EACCES;
tag = ublk_pos_to_tag(iocb->ki_pos); q_id = ublk_pos_to_hwq(iocb->ki_pos); buf_off = ublk_pos_to_buf_off(iocb->ki_pos); + is_integrity = ublk_pos_is_integrity(iocb->ki_pos);
if (q_id >= ub->dev_info.nr_hw_queues) return -EINVAL;
ubq = ublk_get_queue(ub, q_id); @@ -2645,21 +2674,31 @@ ublk_user_copy(struct kiocb *iocb, struct iov_iter *iter, int dir) io = &ubq->ios[tag]; req = __ublk_check_and_get_req(ub, q_id, tag, io); if (!req) return -EINVAL;
- if (buf_off > blk_rq_bytes(req)) { + if (is_integrity) { + struct blk_integrity *bi = &req->q->limits.integrity; + + data_len = bio_integrity_bytes(bi, blk_rq_sectors(req)); + } else { + data_len = blk_rq_bytes(req); + } + if (buf_off > data_len) { ret = -EINVAL; goto out; }
if (!ublk_check_ubuf_dir(req, dir)) { ret = -EACCES; goto out; }
- ret = ublk_copy_user_pages(req, buf_off, iter, dir); + if (is_integrity) + ret = ublk_copy_user_integrity(req, buf_off, iter, dir); + else + ret = ublk_copy_user_pages(req, buf_off, iter, dir);
out: ublk_put_req_ref(io, req); return ret; }
From: Stanley Zhang stazhang@purestorage.com
If the UBLK_PARAM_TYPE_INTEGRITY flag is set, validate the integrity parameters and apply them to the blk_integrity limits. UBLK_PARAM_TYPE_INTEGRITY requires CONFIG_BLK_DEV_INTEGRITY=y, UBLK_F_USER_COPY, and metadata_size > 0. Reuse the block metadata ioctl LBMD_PI_CAP_* and LBMD_PI_CSUM_* constants from the linux/fs.h UAPI header for the flags and csum_type field values. The struct ublk_param_integrity validations are based on the checks in blk_validate_integrity_limits(). Any invalid parameters should be rejected before being applied to struct blk_integrity.
Signed-off-by: Stanley Zhang stazhang@purestorage.com [csander: add param validation] Signed-off-by: Caleb Sander Mateos csander@purestorage.com --- drivers/block/ublk_drv.c | 92 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 91 insertions(+), 1 deletion(-)
diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c index 4da5d8ff1e1d..2893a9172220 100644 --- a/drivers/block/ublk_drv.c +++ b/drivers/block/ublk_drv.c @@ -42,10 +42,12 @@ #include <linux/mm.h> #include <asm/page.h> #include <linux/task_work.h> #include <linux/namei.h> #include <linux/kref.h> +#include <linux/blk-integrity.h> +#include <uapi/linux/fs.h> #include <uapi/linux/ublk_cmd.h>
#define UBLK_MINORS (1U << MINORBITS)
#define UBLK_INVALID_BUF_IDX ((u16)-1) @@ -81,11 +83,12 @@
/* All UBLK_PARAM_TYPE_* should be included here */ #define UBLK_PARAM_TYPE_ALL \ (UBLK_PARAM_TYPE_BASIC | UBLK_PARAM_TYPE_DISCARD | \ UBLK_PARAM_TYPE_DEVT | UBLK_PARAM_TYPE_ZONED | \ - UBLK_PARAM_TYPE_DMA_ALIGN | UBLK_PARAM_TYPE_SEGMENT) + UBLK_PARAM_TYPE_DMA_ALIGN | UBLK_PARAM_TYPE_SEGMENT | \ + UBLK_PARAM_TYPE_INTEGRITY)
struct ublk_uring_cmd_pdu { /* * Store requests in same batch temporarily for queuing them to * daemon context. @@ -613,10 +616,57 @@ static void ublk_dev_param_basic_apply(struct ublk_device *ub) set_disk_ro(ub->ub_disk, true);
set_capacity(ub->ub_disk, p->dev_sectors); }
+static int ublk_integrity_flags(u32 flags) +{ + int ret_flags = 0; + + if (flags & LBMD_PI_CAP_INTEGRITY) { + flags &= ~LBMD_PI_CAP_INTEGRITY; + ret_flags |= BLK_INTEGRITY_DEVICE_CAPABLE; + } + if (flags & LBMD_PI_CAP_REFTAG) { + flags &= ~LBMD_PI_CAP_REFTAG; + ret_flags |= BLK_INTEGRITY_REF_TAG; + } + return flags ? -EINVAL : ret_flags; +} + +static int ublk_integrity_pi_tuple_size(u8 csum_type) +{ + switch (csum_type) { + case LBMD_PI_CSUM_NONE: + return 0; + case LBMD_PI_CSUM_IP: + case LBMD_PI_CSUM_CRC16_T10DIF: + return 8; + case LBMD_PI_CSUM_CRC64_NVME: + return 16; + default: + return -EINVAL; + } +} + +static enum blk_integrity_checksum ublk_integrity_csum_type(u8 csum_type) +{ + switch (csum_type) { + case LBMD_PI_CSUM_NONE: + return BLK_INTEGRITY_CSUM_NONE; + case LBMD_PI_CSUM_IP: + return BLK_INTEGRITY_CSUM_IP; + case LBMD_PI_CSUM_CRC16_T10DIF: + return BLK_INTEGRITY_CSUM_CRC; + case LBMD_PI_CSUM_CRC64_NVME: + return BLK_INTEGRITY_CSUM_CRC64; + default: + WARN_ON_ONCE(1); + return BLK_INTEGRITY_CSUM_NONE; + } +} + static int ublk_validate_params(const struct ublk_device *ub) { /* basic param is the only one which must be set */ if (ub->params.types & UBLK_PARAM_TYPE_BASIC) { const struct ublk_param_basic *p = &ub->params.basic; @@ -675,10 +725,35 @@ static int ublk_validate_params(const struct ublk_device *ub) return -EINVAL; if (p->max_segment_size < UBLK_MIN_SEGMENT_SIZE) return -EINVAL; }
+ if (ub->params.types & UBLK_PARAM_TYPE_INTEGRITY) { + const struct ublk_param_integrity *p = &ub->params.integrity; + int pi_tuple_size = ublk_integrity_pi_tuple_size(p->csum_type); + int flags = ublk_integrity_flags(p->flags); + + if (!IS_ENABLED(CONFIG_BLK_DEV_INTEGRITY)) + return -EINVAL; + if (!ublk_dev_support_user_copy(ub)) + return -EINVAL; + if (flags < 0) + return flags; + if (pi_tuple_size < 0) + return pi_tuple_size; + if (!p->metadata_size) + return -EINVAL; + if (p->csum_type == LBMD_PI_CSUM_NONE && + (p->flags & LBMD_PI_CAP_REFTAG)) + return -EINVAL; + if (p->pi_offset + pi_tuple_size > p->metadata_size) + return -EINVAL; + if (p->interval_exp < SECTOR_SHIFT || + p->interval_exp > ub->params.basic.logical_bs_shift) + return -EINVAL; + } + return 0; }
static void ublk_apply_params(struct ublk_device *ub) { @@ -2901,10 +2976,25 @@ static int ublk_ctrl_start_dev(struct ublk_device *ub, lim.seg_boundary_mask = ub->params.seg.seg_boundary_mask; lim.max_segment_size = ub->params.seg.max_segment_size; lim.max_segments = ub->params.seg.max_segments; }
+ if (ub->params.types & UBLK_PARAM_TYPE_INTEGRITY) { + const struct ublk_param_integrity *p = &ub->params.integrity; + int pi_tuple_size = ublk_integrity_pi_tuple_size(p->csum_type); + + lim.integrity = (struct blk_integrity) { + .flags = ublk_integrity_flags(p->flags), + .csum_type = ublk_integrity_csum_type(p->csum_type), + .metadata_size = p->metadata_size, + .pi_offset = p->pi_offset, + .interval_exp = p->interval_exp, + .tag_size = p->tag_size, + .pi_tuple_size = pi_tuple_size, + }; + } + if (wait_for_completion_interruptible(&ub->completion) != 0) return -EINTR;
if (ub->ublksrv_tgid != ublksrv_pid) return -EINVAL;
To perform and end-to-end test of integrity information through a ublk device, we need to actually store it somewhere and retrieve it. Add this support to kublk's loop target. It uses a second backing file for the integrity data corresponding to the data stored in the first file. The integrity file is opened without O_DIRECT since it will be accessed at sub-block granularity. Each incoming read/write results in a pair of reads/writes, one to the data file, and one to the integrity file. If either backing I/O fails, the error is propagated to the ublk request. If both backing I/Os read/write some bytes, the ublk request is completed with the smaller of the number of blocks accessed by each I/O.
Signed-off-by: Caleb Sander Mateos csander@purestorage.com --- tools/testing/selftests/ublk/file_backed.c | 63 +++++++++++++++------- 1 file changed, 45 insertions(+), 18 deletions(-)
diff --git a/tools/testing/selftests/ublk/file_backed.c b/tools/testing/selftests/ublk/file_backed.c index db4c176a4f28..b8aacaa928a4 100644 --- a/tools/testing/selftests/ublk/file_backed.c +++ b/tools/testing/selftests/ublk/file_backed.c @@ -33,48 +33,62 @@ static int loop_queue_tgt_rw_io(struct ublk_thread *t, struct ublk_queue *q, unsigned ublk_op = ublksrv_get_op(iod); unsigned zc = ublk_queue_use_zc(q); unsigned auto_zc = ublk_queue_use_auto_zc(q); enum io_uring_op op = ublk_to_uring_op(iod, zc | auto_zc); struct ublk_io *io = ublk_get_io(q, tag); + __u64 offset = iod->start_sector << 9; + __u32 len = iod->nr_sectors << 9; struct io_uring_sqe *sqe[3]; void *addr = io->buf_addr;
+ if (iod->op_flags & UBLK_IO_F_INTEGRITY) { + ublk_io_alloc_sqes(t, sqe, 1); + /* Use second backing file for integrity data */ + io_uring_prep_rw(op, sqe[0], ublk_get_registered_fd(q, 2), + io->integrity_buf, + ublk_integrity_len(q, len), + ublk_integrity_len(q, offset)); + sqe[0]->flags = IOSQE_FIXED_FILE; + /* tgt_data = 1 indicates integrity I/O */ + sqe[0]->user_data = build_user_data(tag, ublk_op, 1, q->q_id, 1); + } + if (!zc || auto_zc) { ublk_io_alloc_sqes(t, sqe, 1); if (!sqe[0]) return -ENOMEM;
io_uring_prep_rw(op, sqe[0], ublk_get_registered_fd(q, 1) /*fds[1]*/, addr, - iod->nr_sectors << 9, - iod->start_sector << 9); + len, + offset); if (auto_zc) sqe[0]->buf_index = tag; io_uring_sqe_set_flags(sqe[0], IOSQE_FIXED_FILE); /* bit63 marks us as tgt io */ sqe[0]->user_data = build_user_data(tag, ublk_op, 0, q->q_id, 1); - return 1; + return !!(iod->op_flags & UBLK_IO_F_INTEGRITY) + 1; }
ublk_io_alloc_sqes(t, sqe, 3);
io_uring_prep_buf_register(sqe[0], q, tag, q->q_id, io->buf_index); sqe[0]->flags |= IOSQE_CQE_SKIP_SUCCESS | IOSQE_IO_HARDLINK; sqe[0]->user_data = build_user_data(tag, ublk_cmd_op_nr(sqe[0]->cmd_op), 0, q->q_id, 1);
io_uring_prep_rw(op, sqe[1], ublk_get_registered_fd(q, 1) /*fds[1]*/, 0, - iod->nr_sectors << 9, - iod->start_sector << 9); + len, + offset); sqe[1]->buf_index = tag; sqe[1]->flags |= IOSQE_FIXED_FILE | IOSQE_IO_HARDLINK; sqe[1]->user_data = build_user_data(tag, ublk_op, 0, q->q_id, 1);
io_uring_prep_buf_unregister(sqe[2], q, tag, q->q_id, io->buf_index); sqe[2]->user_data = build_user_data(tag, ublk_cmd_op_nr(sqe[2]->cmd_op), 0, q->q_id, 1);
- return 2; + return !!(iod->op_flags & UBLK_IO_F_INTEGRITY) + 2; }
static int loop_queue_tgt_io(struct ublk_thread *t, struct ublk_queue *q, int tag) { const struct ublksrv_io_desc *iod = ublk_get_iod(q, tag); @@ -117,16 +131,21 @@ static void ublk_loop_io_done(struct ublk_thread *t, struct ublk_queue *q, { unsigned tag = user_data_to_tag(cqe->user_data); unsigned op = user_data_to_op(cqe->user_data); struct ublk_io *io = ublk_get_io(q, tag);
- if (cqe->res < 0 || op != ublk_cmd_op_nr(UBLK_U_IO_UNREGISTER_IO_BUF)) { - if (!io->result) - io->result = cqe->res; - if (cqe->res < 0) - ublk_err("%s: io failed op %x user_data %lx\n", - __func__, op, cqe->user_data); + if (cqe->res < 0) { + io->result = cqe->res; + ublk_err("%s: io failed op %x user_data %lx\n", + __func__, op, cqe->user_data); + } else if (op != ublk_cmd_op_nr(UBLK_U_IO_UNREGISTER_IO_BUF)) { + __s32 data_len = user_data_to_tgt_data(cqe->user_data) + ? ublk_integrity_data_len(q, cqe->res) + : cqe->res; + + if (!io->result || data_len < io->result) + io->result = data_len; }
/* buffer register op is IOSQE_CQE_SKIP_SUCCESS */ if (op == ublk_cmd_op_nr(UBLK_U_IO_REGISTER_IO_BUF)) io->tgt_ios += 1; @@ -136,10 +155,11 @@ static void ublk_loop_io_done(struct ublk_thread *t, struct ublk_queue *q, }
static int ublk_loop_tgt_init(const struct dev_ctx *ctx, struct ublk_dev *dev) { unsigned long long bytes; + unsigned long blocks; int ret; struct ublk_params p = { .types = UBLK_PARAM_TYPE_BASIC | UBLK_PARAM_TYPE_DMA_ALIGN, .basic = { .attrs = UBLK_ATTR_VOLATILE_CACHE, @@ -152,27 +172,34 @@ static int ublk_loop_tgt_init(const struct dev_ctx *ctx, struct ublk_dev *dev) .dma = { .alignment = 511, }, };
+ ublk_set_integrity_params(ctx, &p); if (ctx->auto_zc_fallback) { ublk_err("%s: not support auto_zc_fallback\n", __func__); return -EINVAL; } - if (ctx->metadata_size) { - ublk_err("%s: integrity not supported\n", __func__); - return -EINVAL; - }
+ /* Use O_DIRECT only for data file */ ret = backing_file_tgt_init(dev, 1); if (ret) return ret;
- if (dev->tgt.nr_backing_files != 1) + /* Expect a second file for integrity data */ + if (dev->tgt.nr_backing_files != 1 + !!ctx->metadata_size) return -EINVAL;
- bytes = dev->tgt.backing_file_size[0]; + blocks = dev->tgt.backing_file_size[0] >> p.basic.logical_bs_shift; + if (ctx->metadata_size) { + unsigned long metadata_blocks = + dev->tgt.backing_file_size[1] / ctx->metadata_size; + + /* Ensure both data and integrity data fit in backing files */ + blocks = min(blocks, metadata_blocks); + } + bytes = blocks << p.basic.logical_bs_shift; dev->tgt.dev_size = bytes; p.basic.dev_sectors = bytes >> 9; dev->tgt.params = p;
return 0;
Add integrity param command line arguments to kublk. Plumb these to struct ublk_params for the null and fault_inject targets, as they don't need to actually read or write the integrity data. Forbid the integrity params for loop or stripe until the integrity data copy is implemented.
Signed-off-by: Caleb Sander Mateos csander@purestorage.com --- tools/testing/selftests/ublk/fault_inject.c | 1 + tools/testing/selftests/ublk/file_backed.c | 4 ++ tools/testing/selftests/ublk/kublk.c | 44 +++++++++++++++++++++ tools/testing/selftests/ublk/kublk.h | 21 ++++++++++ tools/testing/selftests/ublk/null.c | 1 + tools/testing/selftests/ublk/stripe.c | 4 ++ 6 files changed, 75 insertions(+)
diff --git a/tools/testing/selftests/ublk/fault_inject.c b/tools/testing/selftests/ublk/fault_inject.c index b227bd78b252..3b897f69c014 100644 --- a/tools/testing/selftests/ublk/fault_inject.c +++ b/tools/testing/selftests/ublk/fault_inject.c @@ -31,10 +31,11 @@ static int ublk_fault_inject_tgt_init(const struct dev_ctx *ctx, .io_min_shift = 9, .max_sectors = info->max_io_buf_bytes >> 9, .dev_sectors = dev_size >> 9, }, }; + ublk_set_integrity_params(ctx, &dev->tgt.params);
dev->private_data = (void *)(unsigned long)(ctx->fault_inject.delay_us * 1000); return 0; }
diff --git a/tools/testing/selftests/ublk/file_backed.c b/tools/testing/selftests/ublk/file_backed.c index 269d5f124e06..c14ce6608696 100644 --- a/tools/testing/selftests/ublk/file_backed.c +++ b/tools/testing/selftests/ublk/file_backed.c @@ -156,10 +156,14 @@ static int ublk_loop_tgt_init(const struct dev_ctx *ctx, struct ublk_dev *dev)
if (ctx->auto_zc_fallback) { ublk_err("%s: not support auto_zc_fallback\n", __func__); return -EINVAL; } + if (ctx->metadata_size) { + ublk_err("%s: integrity not supported\n", __func__); + return -EINVAL; + }
ret = backing_file_tgt_init(dev); if (ret) return ret;
diff --git a/tools/testing/selftests/ublk/kublk.c b/tools/testing/selftests/ublk/kublk.c index 185ba553686a..cf58d3b60ace 100644 --- a/tools/testing/selftests/ublk/kublk.c +++ b/tools/testing/selftests/ublk/kublk.c @@ -1,10 +1,11 @@ /* SPDX-License-Identifier: MIT */ /* * Description: uring_cmd based ublk */
+#include <linux/fs.h> #include "kublk.h"
#define MAX_NR_TGT_ARG 64
unsigned int ublk_dbg_mask = UBLK_LOG; @@ -1547,10 +1548,12 @@ static void __cmd_create_help(char *exe, bool recovery) printf("%s %s -t [null|loop|stripe|fault_inject] [-q nr_queues] [-d depth] [-n dev_id]\n", exe, recovery ? "recover" : "add"); printf("\t[--foreground] [--quiet] [-z] [--auto_zc] [--auto_zc_fallback] [--debug_mask mask] [-r 0|1] [-g] [-u]\n"); printf("\t[-e 0|1 ] [-i 0|1] [--no_ublk_fixed_fd]\n"); printf("\t[--nthreads threads] [--per_io_tasks]\n"); + printf("\t[--integrity_capable] [--integrity_reftag] [--metadata_size SIZE] " + "[--pi_offset OFFSET] [--csum_type ip|t10dif|nvme] [--tag_size SIZE]\n"); printf("\t[target options] [backfile1] [backfile2] ...\n"); printf("\tdefault: nr_queues=2(max 32), depth=128(max 1024), dev_id=-1(auto allocation)\n"); printf("\tdefault: nthreads=nr_queues");
for (i = 0; i < ARRAY_SIZE(tgt_ops_list); i++) { @@ -1610,20 +1613,27 @@ int main(int argc, char *argv[]) { "user_copy", 0, NULL, 'u'}, { "size", 1, NULL, 's'}, { "nthreads", 1, NULL, 0 }, { "per_io_tasks", 0, NULL, 0 }, { "no_ublk_fixed_fd", 0, NULL, 0 }, + { "integrity_capable", 0, NULL, 0 }, + { "integrity_reftag", 0, NULL, 0 }, + { "metadata_size", 1, NULL, 0 }, + { "pi_offset", 1, NULL, 0 }, + { "csum_type", 1, NULL, 0 }, + { "tag_size", 1, NULL, 0 }, { 0, 0, 0, 0 } }; const struct ublk_tgt_ops *ops = NULL; int option_idx, opt; const char *cmd = argv[1]; struct dev_ctx ctx = { .queue_depth = 128, .nr_hw_queues = 2, .dev_id = -1, .tgt_type = "unknown", + .csum_type = LBMD_PI_CSUM_NONE, }; int ret = -EINVAL, i; int tgt_argc = 1; char *tgt_argv[MAX_NR_TGT_ARG] = { NULL }; int value; @@ -1694,10 +1704,32 @@ int main(int argc, char *argv[]) ctx.nthreads = strtol(optarg, NULL, 10); if (!strcmp(longopts[option_idx].name, "per_io_tasks")) ctx.per_io_tasks = 1; if (!strcmp(longopts[option_idx].name, "no_ublk_fixed_fd")) ctx.no_ublk_fixed_fd = 1; + if (!strcmp(longopts[option_idx].name, "integrity_capable")) + ctx.integrity_flags |= LBMD_PI_CAP_INTEGRITY; + if (!strcmp(longopts[option_idx].name, "integrity_reftag")) + ctx.integrity_flags |= LBMD_PI_CAP_REFTAG; + if (!strcmp(longopts[option_idx].name, "metadata_size")) + ctx.metadata_size = strtoul(optarg, NULL, 0); + if (!strcmp(longopts[option_idx].name, "pi_offset")) + ctx.pi_offset = strtoul(optarg, NULL, 0); + if (!strcmp(longopts[option_idx].name, "csum_type")) { + if (!strcmp(optarg, "ip")) { + ctx.csum_type = LBMD_PI_CSUM_IP; + } else if (!strcmp(optarg, "t10dif")) { + ctx.csum_type = LBMD_PI_CSUM_CRC16_T10DIF; + } else if (!strcmp(optarg, "nvme")) { + ctx.csum_type = LBMD_PI_CSUM_CRC64_NVME; + } else { + ublk_err("invalid csum_type: %s\n", optarg); + return -EINVAL; + } + } + if (!strcmp(longopts[option_idx].name, "tag_size")) + ctx.tag_size = strtoul(optarg, NULL, 0); break; case '?': /* * target requires every option must have argument */ @@ -1736,10 +1768,22 @@ int main(int argc, char *argv[]) ctx.auto_zc_fallback > 1) { fprintf(stderr, "too many data copy modes specified\n"); return -EINVAL; }
+ if (!ctx.metadata_size && (ctx.integrity_flags || + ctx.pi_offset || + ctx.csum_type != LBMD_PI_CSUM_NONE || + ctx.tag_size)) { + ublk_err("integrity parameters require metadata_size\n"); + return -EINVAL; + } + if (ctx.metadata_size && !(ctx.flags & UBLK_F_USER_COPY)) { + ublk_err("integrity requires user_copy\n"); + return -EINVAL; + } + i = optind; while (i < argc && ctx.nr_files < MAX_BACK_FILES) { ctx.files[ctx.nr_files++] = argv[i++]; }
diff --git a/tools/testing/selftests/ublk/kublk.h b/tools/testing/selftests/ublk/kublk.h index 8a83b90ec603..d00f2b465cdf 100644 --- a/tools/testing/selftests/ublk/kublk.h +++ b/tools/testing/selftests/ublk/kublk.h @@ -76,10 +76,15 @@ struct dev_ctx { unsigned int fg:1; unsigned int recovery:1; unsigned int auto_zc_fallback:1; unsigned int per_io_tasks:1; unsigned int no_ublk_fixed_fd:1; + __u32 integrity_flags; + __u8 metadata_size; + __u8 pi_offset; + __u8 csum_type; + __u8 tag_size;
int _evtfd; int _shmid;
/* built from shmem, only for ublk_dump_dev() */ @@ -200,10 +205,26 @@ struct ublk_dev { void *private_data; };
extern int ublk_queue_io_cmd(struct ublk_thread *t, struct ublk_io *io);
+static inline void ublk_set_integrity_params(const struct dev_ctx *ctx, + struct ublk_params *params) +{ + if (!ctx->metadata_size) + return; + + params->types |= UBLK_PARAM_TYPE_INTEGRITY; + params->integrity = (struct ublk_param_integrity) { + .flags = ctx->integrity_flags, + .interval_exp = params->basic.logical_bs_shift, + .metadata_size = ctx->metadata_size, + .pi_offset = ctx->pi_offset, + .csum_type = ctx->csum_type, + .tag_size = ctx->tag_size, + }; +}
static inline int ublk_io_auto_zc_fallback(const struct ublksrv_io_desc *iod) { return !!(iod->op_flags & UBLK_IO_F_NEED_REG_BUF); } diff --git a/tools/testing/selftests/ublk/null.c b/tools/testing/selftests/ublk/null.c index 280043f6b689..3aa162f08476 100644 --- a/tools/testing/selftests/ublk/null.c +++ b/tools/testing/selftests/ublk/null.c @@ -34,10 +34,11 @@ static int ublk_null_tgt_init(const struct dev_ctx *ctx, struct ublk_dev *dev) .seg_boundary_mask = 4095, .max_segment_size = 32 << 10, .max_segments = 32, }, }; + ublk_set_integrity_params(ctx, &dev->tgt.params);
if (info->flags & UBLK_F_SUPPORT_ZERO_COPY) dev->tgt.sq_depth = dev->tgt.cq_depth = 2 * info->queue_depth; return 0; } diff --git a/tools/testing/selftests/ublk/stripe.c b/tools/testing/selftests/ublk/stripe.c index fd412e1f01c0..d4aaf3351d71 100644 --- a/tools/testing/selftests/ublk/stripe.c +++ b/tools/testing/selftests/ublk/stripe.c @@ -296,10 +296,14 @@ static int ublk_stripe_tgt_init(const struct dev_ctx *ctx, struct ublk_dev *dev)
if (ctx->auto_zc_fallback) { ublk_err("%s: not support auto_zc_fallback\n", __func__); return -EINVAL; } + if (ctx->metadata_size) { + ublk_err("%s: integrity not supported\n", __func__); + return -EINVAL; + }
if ((chunk_size & (chunk_size - 1)) || !chunk_size) { ublk_err("invalid chunk size %u\n", chunk_size); return -EINVAL; }
The PI tuple must be contained within the metadata value, so validate that pi_offset + pi_tuple_size <= metadata_size. This guards against block drivers that report invalid pi_offset values.
Signed-off-by: Caleb Sander Mateos csander@purestorage.com --- block/blk-settings.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/block/blk-settings.c b/block/blk-settings.c index 51401f08ce05..d138abc973bb 100644 --- a/block/blk-settings.c +++ b/block/blk-settings.c @@ -159,14 +159,13 @@ static int blk_validate_integrity_limits(struct queue_limits *lim) (bi->flags & BLK_INTEGRITY_REF_TAG)) { pr_warn("ref tag not support without checksum.\n"); return -EINVAL; }
- if (bi->pi_tuple_size > bi->metadata_size) { - pr_warn("pi_tuple_size (%u) exceeds metadata_size (%u)\n", - bi->pi_tuple_size, - bi->metadata_size); + if (bi->pi_offset + bi->pi_tuple_size > bi->metadata_size) { + pr_warn("pi_offset (%u) + pi_tuple_size (%u) exceeds metadata_size (%u)\n", + bi->pi_offset, bi->pi_tuple_size, bi->metadata_size); return -EINVAL; }
switch (bi->csum_type) { case BLK_INTEGRITY_CSUM_NONE:
Factor a helper function ublk_copy_user_bvec() out of ublk_copy_user_pages(). It will be used for copying integrity data too.
Signed-off-by: Caleb Sander Mateos csander@purestorage.com --- drivers/block/ublk_drv.c | 52 +++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 22 deletions(-)
diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c index d3652ceba96d..0499add560b5 100644 --- a/drivers/block/ublk_drv.c +++ b/drivers/block/ublk_drv.c @@ -987,10 +987,39 @@ static const struct block_device_operations ub_fops = { .open = ublk_open, .free_disk = ublk_free_disk, .report_zones = ublk_report_zones, };
+static bool ublk_copy_user_bvec(struct bio_vec bv, unsigned *offset, + struct iov_iter *uiter, int dir, size_t *done) +{ + unsigned len; + void *bv_buf; + size_t copied; + + if (*offset >= bv.bv_len) { + *offset -= bv.bv_len; + return true; + } + + len = bv.bv_len - *offset; + bv_buf = kmap_local_page(bv.bv_page) + bv.bv_offset + *offset; + if (dir == ITER_DEST) + copied = copy_to_iter(bv_buf, len, uiter); + else + copied = copy_from_iter(bv_buf, len, uiter); + + kunmap_local(bv_buf); + + *done += copied; + if (copied < len) + return false; + + *offset = 0; + return true; +} + /* * Copy data between request pages and io_iter, and 'offset' * is the start point of linear offset of request. */ static size_t ublk_copy_user_pages(const struct request *req, @@ -999,33 +1028,12 @@ static size_t ublk_copy_user_pages(const struct request *req, struct req_iterator iter; struct bio_vec bv; size_t done = 0;
rq_for_each_segment(bv, req, iter) { - unsigned len; - void *bv_buf; - size_t copied; - - if (offset >= bv.bv_len) { - offset -= bv.bv_len; - continue; - } - - len = bv.bv_len - offset; - bv_buf = kmap_local_page(bv.bv_page) + bv.bv_offset + offset; - if (dir == ITER_DEST) - copied = copy_to_iter(bv_buf, len, uiter); - else - copied = copy_from_iter(bv_buf, len, uiter); - - kunmap_local(bv_buf); - - done += copied; - if (copied < len) + if (!ublk_copy_user_bvec(bv, &offset, uiter, dir, &done)) break; - - offset = 0; } return done; }
static inline bool ublk_need_map_req(const struct request *req)
ublk_ch_read_iter() and ublk_ch_write_iter() are nearly identical except for the iter direction. Split out a helper function ublk_user_copy() to reduce the code duplication as these functions are about to get larger.
Signed-off-by: Caleb Sander Mateos csander@purestorage.com --- drivers/block/ublk_drv.c | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-)
diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c index 0499add560b5..e39ed8a0f0ae 100644 --- a/drivers/block/ublk_drv.c +++ b/drivers/block/ublk_drv.c @@ -2657,42 +2657,36 @@ static struct request *ublk_check_and_get_req(struct kiocb *iocb, fail: ublk_put_req_ref(*io, req); return ERR_PTR(-EACCES); }
-static ssize_t ublk_ch_read_iter(struct kiocb *iocb, struct iov_iter *to) +static ssize_t +ublk_user_copy(struct kiocb *iocb, struct iov_iter *iter, int dir) { struct request *req; struct ublk_io *io; size_t buf_off; size_t ret;
- req = ublk_check_and_get_req(iocb, to, &buf_off, ITER_DEST, &io); + req = ublk_check_and_get_req(iocb, iter, &buf_off, dir, &io); if (IS_ERR(req)) return PTR_ERR(req);
- ret = ublk_copy_user_pages(req, buf_off, to, ITER_DEST); + ret = ublk_copy_user_pages(req, buf_off, iter, dir); ublk_put_req_ref(io, req);
return ret; }
-static ssize_t ublk_ch_write_iter(struct kiocb *iocb, struct iov_iter *from) +static ssize_t ublk_ch_read_iter(struct kiocb *iocb, struct iov_iter *to) { - struct request *req; - struct ublk_io *io; - size_t buf_off; - size_t ret; - - req = ublk_check_and_get_req(iocb, from, &buf_off, ITER_SOURCE, &io); - if (IS_ERR(req)) - return PTR_ERR(req); - - ret = ublk_copy_user_pages(req, buf_off, from, ITER_SOURCE); - ublk_put_req_ref(io, req); + return ublk_user_copy(iocb, to, ITER_DEST); +}
- return ret; +static ssize_t ublk_ch_write_iter(struct kiocb *iocb, struct iov_iter *from) +{ + return ublk_user_copy(iocb, from, ITER_SOURCE); }
static const struct file_operations ublk_ch_fops = { .owner = THIS_MODULE, .open = ublk_ch_open,
ublk_check_and_get_req() has a single callsite in ublk_user_copy(). It takes a ton of arguments in order to pass local variables from ublk_user_copy() to ublk_check_and_get_req() and vice versa. And more are about to be added. Combine the functions to reduce the argument passing noise.
Signed-off-by: Caleb Sander Mateos csander@purestorage.com --- drivers/block/ublk_drv.c | 51 ++++++++++++++-------------------------- 1 file changed, 18 insertions(+), 33 deletions(-)
diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c index e39ed8a0f0ae..273d580ddc46 100644 --- a/drivers/block/ublk_drv.c +++ b/drivers/block/ublk_drv.c @@ -2612,70 +2612,55 @@ static inline bool ublk_check_ubuf_dir(const struct request *req, return true;
return false; }
-static struct request *ublk_check_and_get_req(struct kiocb *iocb, - struct iov_iter *iter, size_t *off, int dir, - struct ublk_io **io) +static ssize_t +ublk_user_copy(struct kiocb *iocb, struct iov_iter *iter, int dir) { struct ublk_device *ub = iocb->ki_filp->private_data; struct ublk_queue *ubq; struct request *req; + struct ublk_io *io; size_t buf_off; u16 tag, q_id; + ssize_t ret;
if (!user_backed_iter(iter)) - return ERR_PTR(-EACCES); + return -EACCES;
if (ub->dev_info.state == UBLK_S_DEV_DEAD) - return ERR_PTR(-EACCES); + return -EACCES;
tag = ublk_pos_to_tag(iocb->ki_pos); q_id = ublk_pos_to_hwq(iocb->ki_pos); buf_off = ublk_pos_to_buf_off(iocb->ki_pos);
if (q_id >= ub->dev_info.nr_hw_queues) - return ERR_PTR(-EINVAL); + return -EINVAL;
ubq = ublk_get_queue(ub, q_id); if (!ublk_dev_support_user_copy(ub)) - return ERR_PTR(-EACCES); + return -EACCES;
if (tag >= ub->dev_info.queue_depth) - return ERR_PTR(-EINVAL); + return -EINVAL;
- *io = &ubq->ios[tag]; - req = __ublk_check_and_get_req(ub, q_id, tag, *io, buf_off); + io = &ubq->ios[tag]; + req = __ublk_check_and_get_req(ub, q_id, tag, io, buf_off); if (!req) - return ERR_PTR(-EINVAL); - - if (!ublk_check_ubuf_dir(req, dir)) - goto fail; - - *off = buf_off; - return req; -fail: - ublk_put_req_ref(*io, req); - return ERR_PTR(-EACCES); -} - -static ssize_t -ublk_user_copy(struct kiocb *iocb, struct iov_iter *iter, int dir) -{ - struct request *req; - struct ublk_io *io; - size_t buf_off; - size_t ret; + return -EINVAL;
- req = ublk_check_and_get_req(iocb, iter, &buf_off, dir, &io); - if (IS_ERR(req)) - return PTR_ERR(req); + if (!ublk_check_ubuf_dir(req, dir)) { + ret = -EACCES; + goto out; + }
ret = ublk_copy_user_pages(req, buf_off, iter, dir); - ublk_put_req_ref(io, req);
+out: + ublk_put_req_ref(io, req); return ret; }
static ssize_t ublk_ch_read_iter(struct kiocb *iocb, struct iov_iter *to) {
ublk user copy syscalls may be issued from any task, so they take a reference count on the struct ublk_io to check whether it is owned by the ublk server and prevent a concurrent UBLK_IO_COMMIT_AND_FETCH_REQ from completing the request. However, if the user copy syscall is issued on the io's daemon task, a concurrent UBLK_IO_COMMIT_AND_FETCH_REQ isn't possible, so the atomic reference count dance is unnecessary. Check for UBLK_IO_FLAG_OWNED_BY_SRV to ensure the request is dispatched to the sever and obtain the request from ublk_io's req field instead of looking it up on the tagset. Skip the reference count increment and decrement. Commit 8a8fe42d765b ("ublk: optimize UBLK_IO_REGISTER_IO_BUF on daemon task") made an analogous optimization for ublk zero copy buffer registration.
Signed-off-by: Caleb Sander Mateos csander@purestorage.com --- drivers/block/ublk_drv.c | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-)
diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c index 042df4de9253..a0fbabd49feb 100644 --- a/drivers/block/ublk_drv.c +++ b/drivers/block/ublk_drv.c @@ -180,11 +180,11 @@ struct ublk_io { /* * The number of uses of this I/O by the ublk server * if user copy or zero copy are enabled: * - UBLK_REFCOUNT_INIT from dispatch to the server * until UBLK_IO_COMMIT_AND_FETCH_REQ - * - 1 for each inflight ublk_ch_{read,write}_iter() call + * - 1 for each inflight ublk_ch_{read,write}_iter() call not on task * - 1 for each io_uring registered buffer not registered on task * The I/O can only be completed once all references are dropped. * User copy and buffer registration operations are only permitted * if the reference count is nonzero. */ @@ -2644,10 +2644,11 @@ ublk_user_copy(struct kiocb *iocb, struct iov_iter *iter, int dir) struct ublk_queue *ubq; struct request *req; struct ublk_io *io; unsigned data_len; bool is_integrity; + bool on_daemon; size_t buf_off; u16 tag, q_id; ssize_t ret;
if (!user_backed_iter(iter)) @@ -2670,13 +2671,24 @@ ublk_user_copy(struct kiocb *iocb, struct iov_iter *iter, int dir)
if (tag >= ub->dev_info.queue_depth) return -EINVAL;
io = &ubq->ios[tag]; - req = __ublk_check_and_get_req(ub, q_id, tag, io); - if (!req) - return -EINVAL; + on_daemon = current == READ_ONCE(io->task); + if (on_daemon) { + /* On daemon, io can't be completed concurrently, so skip ref */ + if (!(io->flags & UBLK_IO_FLAG_OWNED_BY_SRV)) + return -EINVAL; + + req = io->req; + if (!ublk_rq_has_data(req)) + return -EINVAL; + } else { + req = __ublk_check_and_get_req(ub, q_id, tag, io); + if (!req) + return -EINVAL; + }
if (is_integrity) { struct blk_integrity *bi = &req->q->limits.integrity;
data_len = bio_integrity_bytes(bi, blk_rq_sectors(req)); @@ -2697,11 +2709,12 @@ ublk_user_copy(struct kiocb *iocb, struct iov_iter *iter, int dir) ret = ublk_copy_user_integrity(req, buf_off, iter, dir); else ret = ublk_copy_user_pages(req, buf_off, iter, dir);
out: - ublk_put_req_ref(io, req); + if (!on_daemon) + ublk_put_req_ref(io, req); return ret; }
static ssize_t ublk_ch_read_iter(struct kiocb *iocb, struct iov_iter *to) {
Add test case loop_08 to verify the ublk integrity data flow. It uses the kublk loop target to create a ublk device with integrity on top of backing data and integrity files. It then writes to the whole device with fio configured to generate integrity data. Then it reads back the whole device with fio configured to verify the integrity data. It also verifies that injected guard, reftag, and apptag corruptions are correctly detected.
Signed-off-by: Caleb Sander Mateos csander@purestorage.com --- tools/testing/selftests/ublk/Makefile | 1 + tools/testing/selftests/ublk/test_loop_08.sh | 111 +++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100755 tools/testing/selftests/ublk/test_loop_08.sh
diff --git a/tools/testing/selftests/ublk/Makefile b/tools/testing/selftests/ublk/Makefile index d567f90d30b3..e04922085fd5 100644 --- a/tools/testing/selftests/ublk/Makefile +++ b/tools/testing/selftests/ublk/Makefile @@ -32,10 +32,11 @@ TEST_PROGS += test_loop_02.sh TEST_PROGS += test_loop_03.sh TEST_PROGS += test_loop_04.sh TEST_PROGS += test_loop_05.sh TEST_PROGS += test_loop_06.sh TEST_PROGS += test_loop_07.sh +TEST_PROGS += test_loop_08.sh TEST_PROGS += test_stripe_01.sh TEST_PROGS += test_stripe_02.sh TEST_PROGS += test_stripe_03.sh TEST_PROGS += test_stripe_04.sh TEST_PROGS += test_stripe_05.sh diff --git a/tools/testing/selftests/ublk/test_loop_08.sh b/tools/testing/selftests/ublk/test_loop_08.sh new file mode 100755 index 000000000000..ca289cfb2ad4 --- /dev/null +++ b/tools/testing/selftests/ublk/test_loop_08.sh @@ -0,0 +1,111 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 + +. "$(cd "$(dirname "$0")" && pwd)"/test_common.sh + +if ! _have_program fio; then + exit $UBLK_SKIP_CODE +fi + +fio_version=$(fio --version) +if [[ "$fio_version" =~ fio-[0-9]+.[0-9]+$ ]]; then + echo "Requires development fio version with https://github.com/axboe/fio/pull/1992" + exit $UBLK_SKIP_CODE +fi + +TID=loop_08 + +_prep_test "loop" "end-to-end integrity" + +_create_backfile 0 256M +_create_backfile 1 32M # 256M * (64 integrity bytes / 512 data bytes) +integrity_params="--integrity_capable --integrity_reftag + --metadata_size 64 --pi_offset 56 --csum_type t10dif" +dev_id=$(_add_ublk_dev -t loop -u $integrity_params "${UBLK_BACKFILES[@]}") +_check_add_dev $TID $? + +# 1M * (64 integrity bytes / 512 data bytes) = 128K +fio_args="--ioengine io_uring --direct 1 --bsrange 512-1M --iodepth 32 + --md_per_io_size 128K --pi_act 0 --pi_chk GUARD,REFTAG,APPTAG + --filename /dev/ublkb$dev_id" +fio --name fill --rw randwrite $fio_args > /dev/null +err=$? +if [ $err != 0 ]; then + echo "fio fill failed" + _show_result $TID $err +fi + +fio --name verify --rw randread $fio_args > /dev/null +err=$? +if [ $err != 0 ]; then + echo "fio verify failed" + _show_result $TID $err +fi + +fio_err=$(mktemp fio_err_XXXXX) + +# Overwrite 4-byte reftag at offset 56 + 4 = 60 +dd_reftag_args="bs=1 seek=60 count=4 oflag=dsync conv=notrunc status=none" +dd if=/dev/urandom "of=${UBLK_BACKFILES[1]}" $dd_reftag_args +err=$? +if [ $err != 0 ]; then + echo "dd corrupted_reftag failed" + rm -f "$fio_err" + _show_result $TID $err +fi +if fio --name corrupted_reftag --rw randread $fio_args > /dev/null 2> "$fio_err"; then + echo "fio corrupted_reftag unexpectedly succeeded" + rm -f "$fio_err" + _show_result $TID 255 +fi +expected_err="REFTAG compare error: LBA: 0 Expected=0, Actual=" +if ! grep -q "$expected_err" "$fio_err"; then + echo "fio corrupted_reftag message not found: $expected_err" + rm -f "$fio_err" + _show_result $TID 255 +fi +# Reset to 0 +dd if=/dev/zero "of=${UBLK_BACKFILES[1]}" $dd_reftag_args +err=$? +if [ $err != 0 ]; then + echo "dd restore corrupted_reftag failed" + rm -f "$fio_err" + _show_result $TID $err +fi + +dd_data_args="bs=512 count=1 oflag=direct,dsync conv=notrunc status=none" +dd if=/dev/zero "of=${UBLK_BACKFILES[0]}" $dd_data_args +err=$? +if [ $err != 0 ]; then + echo "dd corrupted_data failed" + rm -f "$fio_err" + _show_result $TID $err +fi +if fio --name corrupted_data --rw randread $fio_args > /dev/null 2> "$fio_err"; then + echo "fio corrupted_data unexpectedly succeeded" + rm -f "$fio_err" + _show_result $TID 255 +fi +expected_err="Guard compare error: LBA: 0 Expected=0, Actual=" +if ! grep -q "$expected_err" "$fio_err"; then + echo "fio corrupted_data message not found: $expected_err" + rm -f "$fio_err" + _show_result $TID 255 +fi + +if fio --name bad_apptag --rw randread $fio_args --apptag 0x4321 > /dev/null 2> "$fio_err"; then + echo "fio bad_apptag unexpectedly succeeded" + rm -f "$fio_err" + _show_result $TID 255 +fi +expected_err="APPTAG compare error: LBA: [0-9]* Expected=4321, Actual=1234" +if ! grep -q "$expected_err" "$fio_err"; then + echo "fio bad_apptag message not found: $expected_err" + rm -f "$fio_err" + _show_result $TID 255 +fi + +rm -f "$fio_err" + +_cleanup_test +_show_result $TID 0
ublk_dev_support_user_copy() will be used in ublk_validate_params(). Move these functions next to ublk_{dev,queue}_is_zoned() to avoid needing to forward-declare them.
Signed-off-by: Caleb Sander Mateos csander@purestorage.com --- drivers/block/ublk_drv.c | 60 ++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 30 deletions(-)
diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c index 78f3e22151b9..4da5d8ff1e1d 100644 --- a/drivers/block/ublk_drv.c +++ b/drivers/block/ublk_drv.c @@ -258,10 +258,40 @@ static inline struct ublksrv_io_desc * ublk_get_iod(const struct ublk_queue *ubq, unsigned tag) { return &ubq->io_cmd_buf[tag]; }
+static inline bool ublk_support_zero_copy(const struct ublk_queue *ubq) +{ + return ubq->flags & UBLK_F_SUPPORT_ZERO_COPY; +} + +static inline bool ublk_dev_support_zero_copy(const struct ublk_device *ub) +{ + return ub->dev_info.flags & UBLK_F_SUPPORT_ZERO_COPY; +} + +static inline bool ublk_support_auto_buf_reg(const struct ublk_queue *ubq) +{ + return ubq->flags & UBLK_F_AUTO_BUF_REG; +} + +static inline bool ublk_dev_support_auto_buf_reg(const struct ublk_device *ub) +{ + return ub->dev_info.flags & UBLK_F_AUTO_BUF_REG; +} + +static inline bool ublk_support_user_copy(const struct ublk_queue *ubq) +{ + return ubq->flags & UBLK_F_USER_COPY; +} + +static inline bool ublk_dev_support_user_copy(const struct ublk_device *ub) +{ + return ub->dev_info.flags & UBLK_F_USER_COPY; +} + static inline bool ublk_dev_is_zoned(const struct ublk_device *ub) { return ub->dev_info.flags & UBLK_F_ZONED; }
@@ -656,40 +686,10 @@ static void ublk_apply_params(struct ublk_device *ub)
if (ub->params.types & UBLK_PARAM_TYPE_ZONED) ublk_dev_param_zoned_apply(ub); }
-static inline bool ublk_support_zero_copy(const struct ublk_queue *ubq) -{ - return ubq->flags & UBLK_F_SUPPORT_ZERO_COPY; -} - -static inline bool ublk_dev_support_zero_copy(const struct ublk_device *ub) -{ - return ub->dev_info.flags & UBLK_F_SUPPORT_ZERO_COPY; -} - -static inline bool ublk_support_auto_buf_reg(const struct ublk_queue *ubq) -{ - return ubq->flags & UBLK_F_AUTO_BUF_REG; -} - -static inline bool ublk_dev_support_auto_buf_reg(const struct ublk_device *ub) -{ - return ub->dev_info.flags & UBLK_F_AUTO_BUF_REG; -} - -static inline bool ublk_support_user_copy(const struct ublk_queue *ubq) -{ - return ubq->flags & UBLK_F_USER_COPY; -} - -static inline bool ublk_dev_support_user_copy(const struct ublk_device *ub) -{ - return ub->dev_info.flags & UBLK_F_USER_COPY; -} - static inline bool ublk_need_map_io(const struct ublk_queue *ubq) { return !ublk_support_user_copy(ubq) && !ublk_support_zero_copy(ubq) && !ublk_support_auto_buf_reg(ubq); }
__ublk_check_and_get_req() checks that the passed in offset is within the data length of the specified ublk request. However, only user copy (ublk_check_and_get_req()) supports accessing ublk request data at a nonzero offset. Zero-copy buffer registration (ublk_register_io_buf()) always passes 0 for the offset, so the check is unnecessary. Move the check from __ublk_check_and_get_req() to ublk_check_and_get_req().
Signed-off-by: Caleb Sander Mateos csander@purestorage.com --- drivers/block/ublk_drv.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-)
diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c index 273d580ddc46..7fa0a9f0bfae 100644 --- a/drivers/block/ublk_drv.c +++ b/drivers/block/ublk_drv.c @@ -252,11 +252,11 @@ struct ublk_params_header {
static void ublk_io_release(void *priv); static void ublk_stop_dev_unlocked(struct ublk_device *ub); static void ublk_abort_queue(struct ublk_device *ub, struct ublk_queue *ubq); static inline struct request *__ublk_check_and_get_req(struct ublk_device *ub, - u16 q_id, u16 tag, struct ublk_io *io, size_t offset); + u16 q_id, u16 tag, struct ublk_io *io); static inline unsigned int ublk_req_build_flags(struct request *req);
static inline struct ublksrv_io_desc * ublk_get_iod(const struct ublk_queue *ubq, unsigned tag) { @@ -2245,11 +2245,11 @@ static int ublk_register_io_buf(struct io_uring_cmd *cmd, int ret;
if (!ublk_dev_support_zero_copy(ub)) return -EINVAL;
- req = __ublk_check_and_get_req(ub, q_id, tag, io, 0); + req = __ublk_check_and_get_req(ub, q_id, tag, io); if (!req) return -EINVAL;
ret = io_buffer_register_bvec(cmd, req, ublk_io_release, index, issue_flags); @@ -2539,11 +2539,11 @@ static int ublk_ch_uring_cmd_local(struct io_uring_cmd *cmd, __func__, cmd_op, tag, ret, io ? io->flags : 0); return ret; }
static inline struct request *__ublk_check_and_get_req(struct ublk_device *ub, - u16 q_id, u16 tag, struct ublk_io *io, size_t offset) + u16 q_id, u16 tag, struct ublk_io *io) { struct request *req;
/* * can't use io->req in case of concurrent UBLK_IO_COMMIT_AND_FETCH_REQ, @@ -2560,13 +2560,10 @@ static inline struct request *__ublk_check_and_get_req(struct ublk_device *ub, goto fail_put;
if (!ublk_rq_has_data(req)) goto fail_put;
- if (offset > blk_rq_bytes(req)) - goto fail_put; - return req; fail_put: ublk_put_req_ref(io, req); return NULL; } @@ -2644,14 +2641,19 @@ ublk_user_copy(struct kiocb *iocb, struct iov_iter *iter, int dir)
if (tag >= ub->dev_info.queue_depth) return -EINVAL;
io = &ubq->ios[tag]; - req = __ublk_check_and_get_req(ub, q_id, tag, io, buf_off); + req = __ublk_check_and_get_req(ub, q_id, tag, io); if (!req) return -EINVAL;
+ if (buf_off > blk_rq_bytes(req)) { + ret = -EINVAL; + goto out; + } + if (!ublk_check_ubuf_dir(req, dir)) { ret = -EACCES; goto out; }
A subsequent commit will add support for using a backing file to store integrity data. Since integrity data is accessed in intervals of metadata_size, which may be much smaller than a logical block on the backing device, direct I/O cannot be used. Add an argument to backing_file_tgt_init() to specify the number of files to open for direct I/O. The remaining files will use buffered I/O. For now, continue to request direct I/O for all the files.
Signed-off-by: Caleb Sander Mateos csander@purestorage.com --- tools/testing/selftests/ublk/common.c | 4 ++-- tools/testing/selftests/ublk/file_backed.c | 2 +- tools/testing/selftests/ublk/kublk.h | 2 +- tools/testing/selftests/ublk/stripe.c | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/tools/testing/selftests/ublk/common.c b/tools/testing/selftests/ublk/common.c index 01580a6f8519..d9873d4d50d0 100644 --- a/tools/testing/selftests/ublk/common.c +++ b/tools/testing/selftests/ublk/common.c @@ -10,11 +10,11 @@ void backing_file_tgt_deinit(struct ublk_dev *dev) fsync(dev->fds[i]); close(dev->fds[i]); } }
-int backing_file_tgt_init(struct ublk_dev *dev) +int backing_file_tgt_init(struct ublk_dev *dev, unsigned int nr_direct) { int fd, i;
assert(dev->nr_fds == 1);
@@ -23,11 +23,11 @@ int backing_file_tgt_init(struct ublk_dev *dev) unsigned long bytes; struct stat st;
ublk_dbg(UBLK_DBG_DEV, "%s: file %d: %s\n", __func__, i, file);
- fd = open(file, O_RDWR | O_DIRECT); + fd = open(file, O_RDWR | (i < nr_direct ? O_DIRECT : 0)); if (fd < 0) { ublk_err("%s: backing file %s can't be opened: %s\n", __func__, file, strerror(errno)); return -EBADF; } diff --git a/tools/testing/selftests/ublk/file_backed.c b/tools/testing/selftests/ublk/file_backed.c index c14ce6608696..db4c176a4f28 100644 --- a/tools/testing/selftests/ublk/file_backed.c +++ b/tools/testing/selftests/ublk/file_backed.c @@ -161,11 +161,11 @@ static int ublk_loop_tgt_init(const struct dev_ctx *ctx, struct ublk_dev *dev) if (ctx->metadata_size) { ublk_err("%s: integrity not supported\n", __func__); return -EINVAL; }
- ret = backing_file_tgt_init(dev); + ret = backing_file_tgt_init(dev, 1); if (ret) return ret;
if (dev->tgt.nr_backing_files != 1) return -EINVAL; diff --git a/tools/testing/selftests/ublk/kublk.h b/tools/testing/selftests/ublk/kublk.h index 830b49a7716a..96c66b337bc0 100644 --- a/tools/testing/selftests/ublk/kublk.h +++ b/tools/testing/selftests/ublk/kublk.h @@ -460,8 +460,8 @@ extern const struct ublk_tgt_ops null_tgt_ops; extern const struct ublk_tgt_ops loop_tgt_ops; extern const struct ublk_tgt_ops stripe_tgt_ops; extern const struct ublk_tgt_ops fault_inject_tgt_ops;
void backing_file_tgt_deinit(struct ublk_dev *dev); -int backing_file_tgt_init(struct ublk_dev *dev); +int backing_file_tgt_init(struct ublk_dev *dev, unsigned int nr_direct);
#endif diff --git a/tools/testing/selftests/ublk/stripe.c b/tools/testing/selftests/ublk/stripe.c index d4aaf3351d71..2be1c36438e7 100644 --- a/tools/testing/selftests/ublk/stripe.c +++ b/tools/testing/selftests/ublk/stripe.c @@ -313,11 +313,11 @@ static int ublk_stripe_tgt_init(const struct dev_ctx *ctx, struct ublk_dev *dev) return -EINVAL; }
chunk_shift = ilog2(chunk_size);
- ret = backing_file_tgt_init(dev); + ret = backing_file_tgt_init(dev, dev->tgt.nr_backing_files); if (ret) return ret;
if (!dev->tgt.nr_backing_files || dev->tgt.nr_backing_files > NR_STRIPE) return -EINVAL;
If integrity data is enabled for kublk, allocate an integrity buffer for each I/O. Extend ublk_user_copy() to copy the integrity data between the ublk request and the integrity buffer if the ublksrv_io_desc indicates that the request has integrity data.
Signed-off-by: Caleb Sander Mateos csander@purestorage.com --- tools/testing/selftests/ublk/kublk.c | 41 ++++++++++++++++++++++++---- tools/testing/selftests/ublk/kublk.h | 14 ++++++++++ 2 files changed, 50 insertions(+), 5 deletions(-)
diff --git a/tools/testing/selftests/ublk/kublk.c b/tools/testing/selftests/ublk/kublk.c index cf58d3b60ace..a5c0e4ff9e2a 100644 --- a/tools/testing/selftests/ublk/kublk.c +++ b/tools/testing/selftests/ublk/kublk.c @@ -414,12 +414,14 @@ static void ublk_queue_deinit(struct ublk_queue *q) int nr_ios = q->q_depth;
if (q->io_cmd_buf) munmap(q->io_cmd_buf, ublk_queue_cmd_buf_sz(q));
- for (i = 0; i < nr_ios; i++) + for (i = 0; i < nr_ios; i++) { free(q->ios[i].buf_addr); + free(q->ios[i].integrity_buf); + } }
static void ublk_thread_deinit(struct ublk_thread *t) { io_uring_unregister_buffers(&t->ring); @@ -431,23 +433,25 @@ static void ublk_thread_deinit(struct ublk_thread *t) close(t->ring.ring_fd); t->ring.ring_fd = -1; } }
-static int ublk_queue_init(struct ublk_queue *q, unsigned long long extra_flags) +static int ublk_queue_init(struct ublk_queue *q, unsigned long long extra_flags, + __u8 metadata_size) { struct ublk_dev *dev = q->dev; int depth = dev->dev_info.queue_depth; int i; - int cmd_buf_size, io_buf_size; + int cmd_buf_size, io_buf_size, integrity_size; unsigned long off;
q->tgt_ops = dev->tgt.ops; q->flags = 0; q->q_depth = depth; q->flags = dev->dev_info.flags; q->flags |= extra_flags; + q->metadata_size = metadata_size;
/* Cache fd in queue for fast path access */ q->ublk_fd = dev->fds[0];
cmd_buf_size = ublk_queue_cmd_buf_sz(q); @@ -459,15 +463,27 @@ static int ublk_queue_init(struct ublk_queue *q, unsigned long long extra_flags) q->dev->dev_info.dev_id, q->q_id); goto fail; }
io_buf_size = dev->dev_info.max_io_buf_bytes; + integrity_size = ublk_integrity_len(q, io_buf_size); for (i = 0; i < q->q_depth; i++) { q->ios[i].buf_addr = NULL; q->ios[i].flags = UBLKS_IO_NEED_FETCH_RQ | UBLKS_IO_FREE; q->ios[i].tag = i;
+ if (integrity_size) { + q->ios[i].integrity_buf = malloc(integrity_size); + if (!q->ios[i].integrity_buf) { + ublk_err("ublk dev %d queue %d io %d malloc(%d) failed: %m\n", + dev->dev_info.dev_id, q->q_id, i, + integrity_size); + goto fail; + } + } + + if (ublk_queue_no_buf(q)) continue;
if (posix_memalign((void **)&q->ios[i].buf_addr, getpagesize(), io_buf_size)) { @@ -606,17 +622,17 @@ static void ublk_user_copy(const struct ublk_io *io, __u8 match_ublk_op) const struct ublksrv_io_desc *iod = ublk_get_iod(q, io->tag); __u64 off = ublk_user_copy_offset(q->q_id, io->tag); __u8 ublk_op = ublksrv_get_op(iod); __u32 len = iod->nr_sectors << 9; void *addr = io->buf_addr; + ssize_t copied;
if (ublk_op != match_ublk_op) return;
while (len) { __u32 copy_len = min(len, UBLK_USER_COPY_LEN); - ssize_t copied;
if (ublk_op == UBLK_IO_OP_WRITE) copied = pread(q->ublk_fd, addr, copy_len, off); else if (ublk_op == UBLK_IO_OP_READ) copied = pwrite(q->ublk_fd, addr, copy_len, off); @@ -625,10 +641,24 @@ static void ublk_user_copy(const struct ublk_io *io, __u8 match_ublk_op) assert(copied == (ssize_t)copy_len); addr += copy_len; off += copy_len; len -= copy_len; } + + if (!(iod->op_flags & UBLK_IO_F_INTEGRITY)) + return; + + len = ublk_integrity_len(q, iod->nr_sectors << 9); + off = ublk_user_copy_offset(q->q_id, io->tag); + off += UBLKSRV_IO_INTEGRITY_FLAG; + if (ublk_op == UBLK_IO_OP_WRITE) + copied = pread(q->ublk_fd, io->integrity_buf, len, off); + else if (ublk_op == UBLK_IO_OP_READ) + copied = pwrite(q->ublk_fd, io->integrity_buf, len, off); + else + assert(0); + assert(copied == (ssize_t)len); }
int ublk_queue_io_cmd(struct ublk_thread *t, struct ublk_io *io) { struct ublk_queue *q = ublk_io_to_queue(io); @@ -1011,11 +1041,12 @@ static int ublk_start_daemon(const struct dev_ctx *ctx, struct ublk_dev *dev)
for (i = 0; i < dinfo->nr_hw_queues; i++) { dev->q[i].dev = dev; dev->q[i].q_id = i;
- ret = ublk_queue_init(&dev->q[i], extra_flags); + ret = ublk_queue_init(&dev->q[i], extra_flags, + ctx->metadata_size); if (ret) { ublk_err("ublk dev %d queue %d init queue failed\n", dinfo->dev_id, i); goto fail; } diff --git a/tools/testing/selftests/ublk/kublk.h b/tools/testing/selftests/ublk/kublk.h index d00f2b465cdf..830b49a7716a 100644 --- a/tools/testing/selftests/ublk/kublk.h +++ b/tools/testing/selftests/ublk/kublk.h @@ -110,10 +110,11 @@ struct ublk_ctrl_cmd_data { __u32 len; };
struct ublk_io { char *buf_addr; + void *integrity_buf;
#define UBLKS_IO_NEED_FETCH_RQ (1UL << 0) #define UBLKS_IO_NEED_COMMIT_RQ_COMP (1UL << 1) #define UBLKS_IO_FREE (1UL << 2) #define UBLKS_IO_NEED_GET_DATA (1UL << 3) @@ -173,10 +174,11 @@ struct ublk_queue { /* borrow one bit of ublk uapi flags, which may never be used */ #define UBLKS_Q_AUTO_BUF_REG_FALLBACK (1ULL << 63) #define UBLKS_Q_NO_UBLK_FIXED_FD (1ULL << 62) __u64 flags; int ublk_fd; /* cached ublk char device fd */ + __u8 metadata_size; struct ublk_io ios[UBLK_QUEUE_DEPTH]; };
struct ublk_thread { struct ublk_dev *dev; @@ -222,10 +224,22 @@ static inline void ublk_set_integrity_params(const struct dev_ctx *ctx, .csum_type = ctx->csum_type, .tag_size = ctx->tag_size, }; }
+static inline size_t ublk_integrity_len(const struct ublk_queue *q, size_t len) +{ + /* All targets currently use interval_exp = logical_bs_shift = 9 */ + return (len >> 9) * q->metadata_size; +} + +static inline size_t +ublk_integrity_data_len(const struct ublk_queue *q, size_t integrity_len) +{ + return (integrity_len / q->metadata_size) << 9; +} + static inline int ublk_io_auto_zc_fallback(const struct ublksrv_io_desc *iod) { return !!(iod->op_flags & UBLK_IO_F_NEED_REG_BUF); }
Add test case null_04 to exercise all the different integrity params. It creates 4 different ublk devices with different combinations of integrity arguments and verifies their integrity limits via sysfs and the metadata_size utility.
Signed-off-by: Caleb Sander Mateos csander@purestorage.com --- tools/testing/selftests/ublk/Makefile | 1 + tools/testing/selftests/ublk/test_common.sh | 10 ++ tools/testing/selftests/ublk/test_null_04.sh | 166 +++++++++++++++++++ 3 files changed, 177 insertions(+) create mode 100755 tools/testing/selftests/ublk/test_null_04.sh
diff --git a/tools/testing/selftests/ublk/Makefile b/tools/testing/selftests/ublk/Makefile index 0f6abb95c87a..d567f90d30b3 100644 --- a/tools/testing/selftests/ublk/Makefile +++ b/tools/testing/selftests/ublk/Makefile @@ -24,10 +24,11 @@ TEST_PROGS += test_generic_13.sh TEST_PROGS += test_generic_14.sh
TEST_PROGS += test_null_01.sh TEST_PROGS += test_null_02.sh TEST_PROGS += test_null_03.sh +TEST_PROGS += test_null_04.sh TEST_PROGS += test_loop_01.sh TEST_PROGS += test_loop_02.sh TEST_PROGS += test_loop_03.sh TEST_PROGS += test_loop_04.sh TEST_PROGS += test_loop_05.sh diff --git a/tools/testing/selftests/ublk/test_common.sh b/tools/testing/selftests/ublk/test_common.sh index 6f1c042de40e..9756b0ee876c 100755 --- a/tools/testing/selftests/ublk/test_common.sh +++ b/tools/testing/selftests/ublk/test_common.sh @@ -374,10 +374,20 @@ run_io_and_recover() _ublk_test_top_dir() { cd "$(dirname "$0")" && pwd }
+METADATA_SIZE_PROG="$(_ublk_test_top_dir)/metadata_size" + +_get_metadata_size() +{ + local dev_id=$1 + local field=$2 + + "$METADATA_SIZE_PROG" "/dev/ublkb$dev_id" | grep "$field" | grep -o "[0-9]*" +} + UBLK_PROG=$(_ublk_test_top_dir)/kublk UBLK_TEST_QUIET=1 UBLK_TEST_SHOW_RESULT=1 UBLK_BACKFILES=() export UBLK_PROG diff --git a/tools/testing/selftests/ublk/test_null_04.sh b/tools/testing/selftests/ublk/test_null_04.sh new file mode 100755 index 000000000000..0b0719ea33a3 --- /dev/null +++ b/tools/testing/selftests/ublk/test_null_04.sh @@ -0,0 +1,166 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 + +. "$(cd "$(dirname "$0")" && pwd)"/test_common.sh + +TID=null_04 + +_prep_test "null" "integrity params" + +dev_id=$(_add_ublk_dev -t null -u --metadata_size 8) +_check_add_dev $TID $? +metadata_size=$(_get_metadata_size "$dev_id" metadata_size) +if [ "$metadata_size" != 8 ]; then + echo "metadata_size $metadata_size != 8" + _show_result $TID 255 +fi +pi_offset=$(_get_metadata_size "$dev_id" pi_offset) +if [ "$pi_offset" != 0 ]; then + echo "pi_offset $pi_offset != 0" + _show_result $TID 255 +fi +pi_tuple_size=$(_get_metadata_size "$dev_id" pi_tuple_size) +if [ "$pi_tuple_size" != 0 ]; then + echo "pi_tuple_size $pi_tuple_size != 0" + _show_result $TID 255 +fi +capable=$(cat "/sys/block/ublkb$dev_id/integrity/device_is_integrity_capable") +if [ "$capable" != 0 ]; then + echo "device_is_integrity_capable $capable != 0" + _show_result $TID 255 +fi +format=$(cat "/sys/block/ublkb$dev_id/integrity/format") +if [ "$format" != nop ]; then + echo "format $format != nop" + _show_result $TID 255 +fi +protection_interval_bytes=$(cat "/sys/block/ublkb$dev_id/integrity/protection_interval_bytes") +if [ "$protection_interval_bytes" != 512 ]; then + echo "protection_interval_bytes $protection_interval_bytes != 512" + _show_result $TID 255 +fi +tag_size=$(cat "/sys/block/ublkb$dev_id/integrity/tag_size") +if [ "$tag_size" != 0 ]; then + echo "tag_size $tag_size != 0" + _show_result $TID 255 +fi +_cleanup_test + +dev_id=$(_add_ublk_dev -t null -u --integrity_capable --metadata_size 64 --pi_offset 56 --csum_type ip) +_check_add_dev $TID $? +metadata_size=$(_get_metadata_size "$dev_id" metadata_size) +if [ "$metadata_size" != 64 ]; then + echo "metadata_size $metadata_size != 64" + _show_result $TID 255 +fi +pi_offset=$(_get_metadata_size "$dev_id" pi_offset) +if [ "$pi_offset" != 56 ]; then + echo "pi_offset $pi_offset != 56" + _show_result $TID 255 +fi +pi_tuple_size=$(_get_metadata_size "$dev_id" pi_tuple_size) +if [ "$pi_tuple_size" != 8 ]; then + echo "pi_tuple_size $pi_tuple_size != 8" + _show_result $TID 255 +fi +capable=$(cat "/sys/block/ublkb$dev_id/integrity/device_is_integrity_capable") +if [ "$capable" != 1 ]; then + echo "device_is_integrity_capable $capable != 1" + _show_result $TID 255 +fi +format=$(cat "/sys/block/ublkb$dev_id/integrity/format") +if [ "$format" != T10-DIF-TYPE3-IP ]; then + echo "format $format != T10-DIF-TYPE3-IP" + _show_result $TID 255 +fi +protection_interval_bytes=$(cat "/sys/block/ublkb$dev_id/integrity/protection_interval_bytes") +if [ "$protection_interval_bytes" != 512 ]; then + echo "protection_interval_bytes $protection_interval_bytes != 512" + _show_result $TID 255 +fi +tag_size=$(cat "/sys/block/ublkb$dev_id/integrity/tag_size") +if [ "$tag_size" != 0 ]; then + echo "tag_size $tag_size != 0" + _show_result $TID 255 +fi +_cleanup_test + +dev_id=$(_add_ublk_dev -t null -u --integrity_reftag --metadata_size 8 --csum_type t10dif) +_check_add_dev $TID $? +metadata_size=$(_get_metadata_size "$dev_id" metadata_size) +if [ "$metadata_size" != 8 ]; then + echo "metadata_size $metadata_size != 8" + _show_result $TID 255 +fi +pi_offset=$(_get_metadata_size "$dev_id" pi_offset) +if [ "$pi_offset" != 0 ]; then + echo "pi_offset $pi_offset != 0" + _show_result $TID 255 +fi +pi_tuple_size=$(_get_metadata_size "$dev_id" pi_tuple_size) +if [ "$pi_tuple_size" != 8 ]; then + echo "pi_tuple_size $pi_tuple_size != 8" + _show_result $TID 255 +fi +capable=$(cat "/sys/block/ublkb$dev_id/integrity/device_is_integrity_capable") +if [ "$capable" != 0 ]; then + echo "device_is_integrity_capable $capable != 0" + _show_result $TID 255 +fi +format=$(cat "/sys/block/ublkb$dev_id/integrity/format") +if [ "$format" != T10-DIF-TYPE1-CRC ]; then + echo "format $format != T10-DIF-TYPE1-CRC" + _show_result $TID 255 +fi +protection_interval_bytes=$(cat "/sys/block/ublkb$dev_id/integrity/protection_interval_bytes") +if [ "$protection_interval_bytes" != 512 ]; then + echo "protection_interval_bytes $protection_interval_bytes != 512" + _show_result $TID 255 +fi +tag_size=$(cat "/sys/block/ublkb$dev_id/integrity/tag_size") +if [ "$tag_size" != 0 ]; then + echo "tag_size $tag_size != 0" + _show_result $TID 255 +fi +_cleanup_test + +dev_id=$(_add_ublk_dev -t null -u --metadata_size 16 --csum_type nvme --tag_size 8) +_check_add_dev $TID $? +metadata_size=$(_get_metadata_size "$dev_id" metadata_size) +if [ "$metadata_size" != 16 ]; then + echo "metadata_size $metadata_size != 16" + _show_result $TID 255 +fi +pi_offset=$(_get_metadata_size "$dev_id" pi_offset) +if [ "$pi_offset" != 0 ]; then + echo "pi_offset $pi_offset != 0" + _show_result $TID 255 +fi +pi_tuple_size=$(_get_metadata_size "$dev_id" pi_tuple_size) +if [ "$pi_tuple_size" != 16 ]; then + echo "pi_tuple_size $pi_tuple_size != 16" + _show_result $TID 255 +fi +capable=$(cat "/sys/block/ublkb$dev_id/integrity/device_is_integrity_capable") +if [ "$capable" != 0 ]; then + echo "device_is_integrity_capable $capable != 0" + _show_result $TID 255 +fi +format=$(cat "/sys/block/ublkb$dev_id/integrity/format") +if [ "$format" != EXT-DIF-TYPE3-CRC64 ]; then + echo "format $format != EXT-DIF-TYPE3-CRC64" + _show_result $TID 255 +fi +protection_interval_bytes=$(cat "/sys/block/ublkb$dev_id/integrity/protection_interval_bytes") +if [ "$protection_interval_bytes" != 512 ]; then + echo "protection_interval_bytes $protection_interval_bytes != 512" + _show_result $TID 255 +fi +tag_size=$(cat "/sys/block/ublkb$dev_id/integrity/tag_size") +if [ "$tag_size" != 8 ]; then + echo "tag_size $tag_size != 8" + _show_result $TID 255 +fi +_cleanup_test + +_show_result $TID 0
Some block device integrity parameters are available in sysfs, but others are only accessible using the FS_IOC_GETLBMD_CAP ioctl. Add a metadata_size utility program to print out the logical block metadata size, PI offset, and PI size within the metadata. Example output: $ metadata_size /dev/ublkb0 metadata_size: 64 pi_offset: 56 pi_tuple_size: 8
Signed-off-by: Caleb Sander Mateos csander@purestorage.com --- tools/testing/selftests/ublk/Makefile | 4 +-- tools/testing/selftests/ublk/metadata_size.c | 36 ++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 tools/testing/selftests/ublk/metadata_size.c
diff --git a/tools/testing/selftests/ublk/Makefile b/tools/testing/selftests/ublk/Makefile index 837977b62417..0f6abb95c87a 100644 --- a/tools/testing/selftests/ublk/Makefile +++ b/tools/testing/selftests/ublk/Makefile @@ -46,14 +46,14 @@ TEST_PROGS += test_stress_03.sh TEST_PROGS += test_stress_04.sh TEST_PROGS += test_stress_05.sh TEST_PROGS += test_stress_06.sh TEST_PROGS += test_stress_07.sh
-TEST_GEN_PROGS_EXTENDED = kublk +TEST_GEN_PROGS_EXTENDED = kublk metadata_size
include ../lib.mk
-$(TEST_GEN_PROGS_EXTENDED): kublk.c null.c file_backed.c common.c stripe.c \ +$(OUTPUT)/kublk: kublk.c null.c file_backed.c common.c stripe.c \ fault_inject.c
check: shellcheck -x -f gcc *.sh diff --git a/tools/testing/selftests/ublk/metadata_size.c b/tools/testing/selftests/ublk/metadata_size.c new file mode 100644 index 000000000000..76ecddf04d25 --- /dev/null +++ b/tools/testing/selftests/ublk/metadata_size.c @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <fcntl.h> +#include <linux/fs.h> +#include <stdio.h> +#include <sys/ioctl.h> + +int main(int argc, char **argv) +{ + struct logical_block_metadata_cap cap = {}; + const char *filename; + int fd; + int result; + + if (argc != 2) { + fprintf(stderr, "Usage: %s BLOCK_DEVICE\n", argv[0]); + return 1; + } + + filename = argv[1]; + fd = open(filename, O_RDONLY); + if (fd < 0) { + perror(filename); + return 1; + } + + result = ioctl(fd, FS_IOC_GETLBMD_CAP, &cap); + if (result < 0) { + perror("ioctl"); + return 1; + } + + printf("metadata_size: %u\n", cap.lbmd_size); + printf("pi_offset: %u\n", cap.lbmd_pi_offset); + printf("pi_tuple_size: %u\n", cap.lbmd_pi_size); + return 0; +}
linux-kselftest-mirror@lists.linaro.org