From 569d2d86fef22de1d32178fe6f1f46f0d59da79d Mon Sep 17 00:00:00 2001 From: Jungwon Lee Date: Thu, 2 Jul 2026 20:20:34 +0900 Subject: [PATCH 1/5] engines/io_uring: drop unused write_opcode assignment on bsg path fio_ioring_cmd_init() assigns ld->write_opcode = bsg_cmd_write_10 when cmd_type=bsg, but that field is never read on the bsg submission path. fio_bsg_uring_cmd_prep() fills bc->cdb[0] directly from io_u->ddir, and ld->write_opcode is only consumed by fio_nvme_uring_cmd_prep() on the nvme path. The assignment has therefore never had any effect. Drop the dead assignment. The surrounding `if (write_mode == WRITE)` branch existed only to hold it; once the assignment is gone the branch is empty, so the check collapses to a plain rejection of unsupported write modes. Fixes: d77ed2b9 ("engines/io_uring: Add bsg support for io_uring_cmd engine") Signed-off-by: Jungwon Lee --- engines/io_uring.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/engines/io_uring.c b/engines/io_uring.c index fd5475cca8..04cdb92c60 100644 --- a/engines/io_uring.c +++ b/engines/io_uring.c @@ -1621,14 +1621,10 @@ static int fio_ioring_cmd_init(struct thread_data *td, struct ioring_data *ld) } else if (o->cmd_type == FIO_URING_CMD_BSG) { ld->bc = calloc(td->o.iodepth, sizeof(struct bsg_cmd)); - if (td_write(td)) { - if (o->write_mode == FIO_URING_CMD_WMODE_WRITE) { - ld->write_opcode = bsg_cmd_write_10; - } else { - log_err("Not Support Write mode in BSG io_uring_cmd\n"); - td_verror(td, EINVAL, "fio_ioring_cmd_init"); - return 1; - } + if (td_write(td) && o->write_mode != FIO_URING_CMD_WMODE_WRITE) { + log_err("Not Support Write mode in BSG io_uring_cmd\n"); + td_verror(td, EINVAL, "fio_ioring_cmd_init"); + return 1; } if (o->readfua) From 8ad9a6e0f84e8a26d6e16d8dccce84f17ae65599 Mon Sep 17 00:00:00 2001 From: Jungwon Lee Date: Fri, 31 Jul 2026 14:25:51 +0900 Subject: [PATCH 2/5] engines/io_uring: drop bsg status parsing from event The bsg completion was being parsed in two places: fio_ioring_cmd_event() decoded the packed SCSI status from cqe->big_cqe[0] into io_u->error, and fio_ioring_cmd_errdetails() re-derived the SCSI status and host status from io_u->error. The NVMe path only does this breakdown once, in errdetails. Drop the bsg-specific decoding from event and store the raw cqe->big_cqe[0] in io_u->error, leaving errdetails as the single place that splits the SCSI status (bits 0-7) and host status (bits 16-23). Fixes: d77ed2b9 ("engines/io_uring: Add bsg support for io_uring_cmd engine") Signed-off-by: Jungwon Lee --- engines/io_uring.c | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/engines/io_uring.c b/engines/io_uring.c index 04cdb92c60..c48a6984b6 100644 --- a/engines/io_uring.c +++ b/engines/io_uring.c @@ -930,16 +930,9 @@ static struct io_u *fio_ioring_cmd_event(struct thread_data *td, int event) io_u->error = ret; } } else if (o->cmd_type == FIO_URING_CMD_BSG) { - /* - * For bsg uring cmd, the big_cqe[0] in cqe contains the packed - * SCSI status, where bits 0-7 hold the device status and bits 16-23 - * contaion the host status - */ - ret = (cqe->big_cqe[0] >> 16) & 0xff; + ret = cqe->big_cqe[0]; if (ret) - io_u->error = -ret; - else - io_u->error = cqe->big_cqe[0] & 0xff; + io_u->error = ret; } ret: From a646e7bcbde0388389011e908548a982f356c0f6 Mon Sep 17 00:00:00 2001 From: Jungwon Lee Date: Thu, 2 Jul 2026 21:28:18 +0900 Subject: [PATCH 3/5] engines/io_uring: add cdb_len option for bsg cmd_type The bsg cmd_type of the io_uring_cmd engine has so far hardcoded READ(10) and WRITE(10) for DDIR_READ and DDIR_WRITE. That caps the supported LBA range at 32 bits and the transfer length at 65535 blocks, which prevents benchmarking devices whose capacity or per-I/O transfer size exceeds those limits, and it also prevents comparing the performance characteristics of different CDB lengths of the same command. Add a new engine option, cdb_len, that selects the SCSI CDB length used for READ/WRITE commands. Supported values are 10, 16, and 32. The 32-byte command is encoded as a variable-length CDB (opcode 0x7F) with the READ(32) and WRITE(32) service actions. The default is 0 (auto): the smallest CDB whose LBA and transfer-length fields can hold the request is chosen, escalating from READ(10)/WRITE(10) to READ(16)/WRITE(16) as the LBA or transfer length grows, mirroring the sg engine. This lets fio drive devices whose capacity or per-I/O size exceeds the 10-byte limits without requiring the user to pick a CDB length up front. The 32-byte CDB shares the same 64-bit LBA and 32-bit length field widths as the 16-byte CDB, so auto-escalation never reaches it and it remains opt-in only (for exercising the variable-length CDB path itself). When an explicit cdb_len is set, requests whose LBA or transfer length would overflow the fields of the selected CDB length are rejected with -EINVAL rather than being silently promoted to a larger CDB. This preserves the user's explicit intent when a specific CDB length is chosen for benchmarking. SYNCHRONIZE CACHE follows cdb_len as well: the 10-byte command (0x35) is issued when cdb_len=10, and the 16-byte command (0x91) is issued otherwise. SBC does not define a 32-byte SYNCHRONIZE CACHE service action, so cdb_len=32 falls back to the 16-byte command. UNMAP has no CDB length selection and keeps its fixed 10-byte CDB regardless of cdb_len. Signed-off-by: Jungwon Lee --- HOWTO.rst | 10 ++++ engines/bsg.c | 131 +++++++++++++++++++++++++++++++++++++-------- engines/bsg.h | 20 ++++++- engines/io_uring.c | 34 +++++++++++- fio.1 | 9 ++++ 5 files changed, 179 insertions(+), 25 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 162115d033..7b6db3aade 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -2598,6 +2598,16 @@ with the caveat that when used on the command line, they must come after the Specifies the type of uring passthrough command to be used. Supported values are nvme and bsg. Default is nvme. +.. option:: cdb_len=int : [io_uring_cmd] + + SCSI CDB length used for bsg :option:`cmd_type`. Supported values are + 0, 10, 16, and 32. Default is 0 (auto): the smallest CDB whose LBA and + transfer-length fields fit the request is chosen, escalating from + READ(10)/WRITE(10) to READ(16)/WRITE(16) as needed, mirroring the sg + engine. Ignored when cmd_type=nvme. When an explicit length is set, a + request whose LBA or transfer length exceeds the field capacity of the + chosen CDB fails with -EINVAL rather than falling back to a larger CDB. + .. option:: hipri [io_uring] [io_uring_cmd] [xnvme] diff --git a/engines/bsg.c b/engines/bsg.c index 916dc2424f..5d7cac456f 100644 --- a/engines/bsg.c +++ b/engines/bsg.c @@ -90,13 +90,14 @@ int fio_bsg_uring_cmd_get_file_size(struct thread_data *td, struct fio_file *f) } void fio_bsg_uring_cmd_init(struct bsg_uring_cmd *cmd, struct bsg_cmd *bc, - struct io_u *io_u, int dxfer_dir) + struct io_u *io_u, int dxfer_dir, + unsigned int cdb_len) { memset(cmd, 0, sizeof(*cmd)); memset(bc->cdb, 0, sizeof(bc->cdb)); cmd->request = (uint64_t)(uintptr_t) bc->cdb; - cmd->request_len = sizeof(bc->cdb); + cmd->request_len = cdb_len; cmd->response = (uint64_t)(uintptr_t) bc->sb; cmd->max_response_len = sizeof(bc->sb); @@ -110,21 +111,52 @@ void fio_bsg_uring_cmd_init(struct bsg_uring_cmd *cmd, struct bsg_cmd *bc, } static int fio_bsg_uring_cmd_rw_lba(struct bsg_cmd *bc, unsigned long long lba, - unsigned long long nlb) + unsigned long long nlb, + unsigned int cdb_len) { - if (lba > MAX_10CDB_LBA || nlb > MAX_10CDB_NLB) { - log_err("offset or nlb is larger than the " - "maximum value of a field within CDB (10)\n"); + switch (cdb_len) { + case 10: + if (lba > MAX_10CDB_LBA || nlb > MAX_10CDB_NLB) { + log_err("offset or nlb is larger than the " + "maximum value of a field within CDB (10)\n"); + return -EINVAL; + } + sgio_set_be32((uint32_t) lba, &bc->cdb[2]); + sgio_set_be16((uint16_t) nlb, &bc->cdb[7]); + return 0; + case 16: + if (lba > MAX_16CDB_LBA || nlb > MAX_16CDB_NLB) { + log_err("offset or nlb is larger than the " + "maximum value of a field within CDB (16)\n"); + return -EINVAL; + } + sgio_set_be64((uint64_t) lba, &bc->cdb[2]); + sgio_set_be32((uint32_t) nlb, &bc->cdb[10]); + return 0; + case 32: + if (lba > MAX_32CDB_LBA || nlb > MAX_32CDB_NLB) { + log_err("offset or nlb is larger than the " + "maximum value of a field within CDB (32)\n"); + return -EINVAL; + } + sgio_set_be64((uint64_t) lba, &bc->cdb[12]); + sgio_set_be32((uint32_t) nlb, &bc->cdb[28]); + return 0; + default: + log_err("unsupported CDB length: %u\n", cdb_len); return -EINVAL; } - sgio_set_be32((uint32_t) lba, &bc->cdb[2]); - sgio_set_be16((uint16_t) nlb, &bc->cdb[7]); +} - return 0; +static void fio_bsg_varlen_cdb_header(struct bsg_cmd *bc, uint16_t sa) +{ + bc->cdb[0] = bsg_cmd_varlen; + sgio_set_be16(sa, &bc->cdb[8]); + bc->cdb[7] = BSG_VARLEN_CDB_ADDITIONAL_LEN; } int fio_bsg_uring_cmd_prep(struct bsg_uring_cmd *cmd, struct io_u *io_u, - struct bsg_cmd *bc, bool fua) + struct bsg_cmd *bc, bool fua, unsigned int cdb_len) { struct bsg_data *data = FILE_ENG_DATA(io_u->file); unsigned long long offset, nlb; @@ -138,21 +170,65 @@ int fio_bsg_uring_cmd_prep(struct bsg_uring_cmd *cmd, struct io_u *io_u, offset = io_u->offset / data->bs; nlb = io_u->xfer_buflen / data->bs; + /* + * cdb_len == 0 selects auto-escalation: pick the smallest CDB whose + * LBA and transfer-length fields can hold this request, mirroring the + * sg engine. READ(10)/WRITE(10) cover a 32-bit LBA and 16-bit length; + * anything larger uses READ(16)/WRITE(16). The 32-byte CDB shares the + * same 64-bit LBA / 32-bit length field widths as the 16-byte CDB, so + * it is never reached by auto-escalation and remains opt-in only. + */ + if (cdb_len == 0) { + if (offset > MAX_10CDB_LBA || nlb > MAX_10CDB_NLB) + cdb_len = 16; + else + cdb_len = 10; + } + switch (io_u->ddir) { case DDIR_READ: - fio_bsg_uring_cmd_init(cmd, bc, io_u, SG_DXFER_FROM_DEV); - bc->cdb[0] = bsg_cmd_read_10; - if (fua) - bc->cdb[1] |= 1 << 3; + fio_bsg_uring_cmd_init(cmd, bc, io_u, SG_DXFER_FROM_DEV, cdb_len); + switch (cdb_len) { + case 10: + bc->cdb[0] = bsg_cmd_read_10; + if (fua) + bc->cdb[1] |= 1 << 3; + break; + case 16: + bc->cdb[0] = bsg_cmd_read_16; + if (fua) + bc->cdb[1] |= 1 << 3; + break; + case 32: + fio_bsg_varlen_cdb_header(bc, BSG_SA_READ_32); + if (fua) + bc->cdb[10] |= 1 << 3; + break; + } break; case DDIR_WRITE: - fio_bsg_uring_cmd_init(cmd, bc, io_u, SG_DXFER_TO_DEV); - bc->cdb[0] = bsg_cmd_write_10; - if (fua) - bc->cdb[1] |= 1 << 3; + fio_bsg_uring_cmd_init(cmd, bc, io_u, SG_DXFER_TO_DEV, cdb_len); + switch (cdb_len) { + case 10: + bc->cdb[0] = bsg_cmd_write_10; + if (fua) + bc->cdb[1] |= 1 << 3; + break; + case 16: + bc->cdb[0] = bsg_cmd_write_16; + if (fua) + bc->cdb[1] |= 1 << 3; + break; + case 32: + fio_bsg_varlen_cdb_header(bc, BSG_SA_WRITE_32); + if (fua) + bc->cdb[10] |= 1 << 3; + break; + } break; case DDIR_TRIM: - fio_bsg_uring_cmd_init(cmd, bc, io_u, SG_DXFER_TO_DEV); + /* UNMAP CDB has fixed 10-byte length regardless of cdb_len */ + fio_bsg_uring_cmd_init(cmd, bc, io_u, SG_DXFER_TO_DEV, 10); bc->cdb[0] = bsg_cmd_unmap; data_len = sizeof(bc->unmap_param) / sizeof(bc->unmap_param[0]); sgio_set_be16((uint16_t) data_len, &bc->cdb[7]); @@ -164,12 +240,23 @@ int fio_bsg_uring_cmd_prep(struct bsg_uring_cmd *cmd, struct io_u *io_u, cmd->dout_xfer_len = data_len; return 0; case DDIR_SYNC: - fio_bsg_uring_cmd_init(cmd, bc, io_u, SG_DXFER_NONE); - bc->cdb[0] = bsg_cmd_sync_cache_10; + /* + * SYNCHRONIZE CACHE has 10-byte (0x35) and 16-byte (0x91) + * CDB only; no 32-byte service action is defined by + * SBC. When cdb_len=32 is requested, fall back to the + * 16-byte CDB. + */ + if (cdb_len == 10) { + fio_bsg_uring_cmd_init(cmd, bc, io_u, SG_DXFER_NONE, 10); + bc->cdb[0] = bsg_cmd_sync_cache_10; + } else { + fio_bsg_uring_cmd_init(cmd, bc, io_u, SG_DXFER_NONE, 16); + bc->cdb[0] = bsg_cmd_sync_cache_16; + } return 0; default: return -ENOTSUP; } - return fio_bsg_uring_cmd_rw_lba(bc, offset, nlb); + return fio_bsg_uring_cmd_rw_lba(bc, offset, nlb, cdb_len); } diff --git a/engines/bsg.h b/engines/bsg.h index da64b9997d..9c367e280b 100644 --- a/engines/bsg.h +++ b/engines/bsg.h @@ -40,19 +40,35 @@ struct bsg_uring_cmd { #endif /* CONFIG_BSG_URING_CMD */ #define MAX_SB 64 +#define MAX_CDB_LEN 32 + #define MAX_10CDB_LBA 0xFFFFFFFFULL #define MAX_10CDB_NLB 0xFFFFU +#define MAX_16CDB_LBA 0xFFFFFFFFFFFFFFFFULL +#define MAX_16CDB_NLB 0xFFFFFFFFU +#define MAX_32CDB_LBA MAX_16CDB_LBA +#define MAX_32CDB_NLB MAX_16CDB_NLB + +/* Variable-length CDB (opcode 0x7F) service actions for 32-byte CDBs */ +#define BSG_VARLEN_CDB_OPCODE 0x7F +#define BSG_VARLEN_CDB_ADDITIONAL_LEN 0x18 /* 24 -> total 32 bytes */ +#define BSG_SA_READ_32 0x0009 +#define BSG_SA_WRITE_32 0x000B enum bsg_io_opcode { bsg_cmd_read_10 = 0x28, + bsg_cmd_read_16 = 0x88, bsg_cmd_read_capacity_10 = 0x25, bsg_cmd_sync_cache_10 = 0x35, + bsg_cmd_sync_cache_16 = 0x91, bsg_cmd_unmap = 0x42, bsg_cmd_write_10 = 0x2A, + bsg_cmd_write_16 = 0x8A, + bsg_cmd_varlen = BSG_VARLEN_CDB_OPCODE, }; struct bsg_cmd { - unsigned char cdb[16]; + unsigned char cdb[MAX_CDB_LEN]; unsigned char sb[MAX_SB]; uint8_t unmap_param[24]; }; @@ -67,6 +83,6 @@ int fio_bsg_uring_cmd_read_capacity(struct thread_data *td, unsigned int *bs, int fio_bsg_uring_cmd_get_file_size(struct thread_data *td, struct fio_file *f); int fio_bsg_uring_cmd_prep(struct bsg_uring_cmd *cmd, struct io_u *io_u, - struct bsg_cmd *bc, bool fua); + struct bsg_cmd *bc, bool fua, unsigned int cdb_len); #endif /* FIO_BSG_H */ diff --git a/engines/io_uring.c b/engines/io_uring.c index c48a6984b6..6d06fead6a 100644 --- a/engines/io_uring.c +++ b/engines/io_uring.c @@ -214,6 +214,7 @@ struct ioring_options { unsigned int prchk; char *pi_chk; enum uring_cmd_type cmd_type; + unsigned int cdb_len; }; static unsigned int enter_flags = IORING_ENTER_GETEVENTS; @@ -528,6 +529,36 @@ static struct fio_option options[] = { .category = FIO_OPT_C_ENGINE, .group = FIO_OPT_G_IOURING, }, + { + .name = "cdb_len", + .lname = "SCSI CDB length", + .type = FIO_OPT_STR, + .off1 = offsetof(struct ioring_options, cdb_len), + .help = "SCSI CDB length for bsg cmd_type (10, 16, or 32). " + "0 (default) auto-escalates to the smallest CDB that " + "fits the request's LBA and transfer length.", + .def = "0", + .posval = { + { .ival = "0", + .oval = 0, + .help = "auto-escalates to the smallest CDB", + }, + { .ival = "10", + .oval = 10, + .help = "CDB length 10", + }, + { .ival = "16", + .oval = 16, + .help = "CDB length 16", + }, + { .ival = "32", + .oval = 32, + .help = "CDB length 32", + }, + }, + .category = FIO_OPT_C_ENGINE, + .group = FIO_OPT_G_IOURING, + }, CMDPRIO_OPTIONS(struct ioring_options, FIO_OPT_G_IOURING), { .name = "md_per_io_size", @@ -845,7 +876,8 @@ static int fio_ioring_cmd_prep(struct thread_data *td, struct io_u *io_u) sqe->len = io_u->xfer_buflen; cmd = (struct bsg_uring_cmd *)sqe->cmd; - return fio_bsg_uring_cmd_prep(cmd, io_u, &ld->bc[io_u->index], ld->fua[io_u->ddir]); + return fio_bsg_uring_cmd_prep(cmd, io_u, &ld->bc[io_u->index], + ld->fua[io_u->ddir], o->cdb_len); } } diff --git a/fio.1 b/fio.1 index 993762f2fe..4ca69e1647 100644 --- a/fio.1 +++ b/fio.1 @@ -2381,6 +2381,15 @@ should be used for the polling thread. Specifies the type of uring passthrough command to be used. Supported values are nvme and bsg. Default is nvme. .TP +.BI (io_uring_cmd)cdb_len \fR=\fPint +SCSI CDB length used for bsg \fBcmd_type\fR. Supported values are 0, 10, 16, +and 32. Default is 0 (auto): the smallest CDB whose LBA and +transfer-length fields fit the request is chosen, escalating from +READ(10)/WRITE(10) to READ(16)/WRITE(16) as needed, mirroring the sg +engine. Ignored when cmd_type=nvme. When an explicit length is set, a +request whose LBA or transfer length exceeds the field capacity of the +chosen CDB fails with -EINVAL rather than falling back to a larger CDB. +.TP .BI (libaio)userspace_reap Normally, with the libaio engine in use, fio will use the \fBio_getevents\fR\|(3) system call to reap newly returned events. With From d66a4fe4cb70d14e3401c441e7d66a4a378f4ad2 Mon Sep 17 00:00:00 2001 From: Jungwon Lee Date: Thu, 2 Jul 2026 10:40:10 +0900 Subject: [PATCH 4/5] engines/io_uring: support write_mode=verify for bsg cmd_type The existing write_mode=verify option is currently only handled for the nvme cmd_type. Extend it to the bsg cmd_type so that SCSI VERIFY commands can be used as the write-side operation of a workload. This is useful for measuring the cost of medium verification against the same address range that would otherwise be written. The VERIFY CDB length follows the cdb_len option: VERIFY(10)=0x2F, VERIFY(16)=0x8F, and VERIFY(32) via the variable-length CDB service action. The data comparison behavior is controlled by a new verify_bytchk option that maps to the CDB BYTCHK field: 0 - medium verification only, no host data transfer (SG_DXFER_NONE) 1 - compare the full transfer against the medium byte by byte 3 - compare a single block against every block in the range For BYTCHK 1 and 3 the command sends data to the device (SG_DXFER_TO_DEV); BYTCHK 3 transfers only a single block while the CDB still carries the full block count. verify_bytchk defaults to 0 and is rejected unless write_mode=verify. VERIFY is the first bsg command that issues SG_DXFER_NONE with a non-zero io_u->xfer_buflen (the buflen encodes the block count that the device verifies against the medium). The previous fio_bsg_uring_cmd_init() lumped SG_DXFER_NONE into the else branch that populates din_xferp/din_xfer_len, which happened to be harmless only because DDIR_SYNC's xfer_buflen was zero. Tighten the else to match SG_DXFER_FROM_DEV explicitly so SG_DXFER_NONE leaves the data transfer fields cleared. writefua is rejected when combined with write_mode=verify. The FUA bit is not defined for the VERIFY command in any of the CDB variants that we support, and silently dropping the flag would hide a user misconfiguration. Signed-off-by: Jungwon Lee --- HOWTO.rst | 24 ++++++++++++++++++- engines/bsg.c | 53 ++++++++++++++++++++++++++++++++++++++++-- engines/bsg.h | 13 ++++++++++- engines/io_uring.c | 58 ++++++++++++++++++++++++++++++++++++++++++---- fio.1 | 25 +++++++++++++++++++- 5 files changed, 163 insertions(+), 10 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 7b6db3aade..1b6a0adf8b 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -3089,7 +3089,11 @@ with the caveat that when used on the command line, they must come after the Use Write Zeroes commands for write operations **verify** - Use Verify commands for write operations + Use Verify commands for write operations. Supported for + both nvme and bsg cmd_type. For bsg, the SCSI VERIFY(10)/ + (16)/(32) opcode is selected by :option:`cdb_len` and the + data comparison behavior by :option:`verify_bytchk`; + :option:`writefua` is not supported in this mode. **zone_append** Use zone append commands for write operations. Requires zonemode=zbd @@ -3100,6 +3104,24 @@ with the caveat that when used on the command line, they must come after the entries with no percentage specified. Example: ``write/60:zeroes/30:uncor/10`` or ``write/50:zeroes/:uncor/`` +.. option:: verify_bytchk=int : [io_uring_cmd] + + BYTCHK field for the SCSI VERIFY command issued when + :option:`write_mode` is ``verify`` and :option:`cmd_type` is ``bsg``. + Only valid with write_mode=verify. Defaults to 0. + + **0** + Medium verification only; no data is transferred to + the device. + + **1** + The device compares the full transfer against the data + stored on the medium, byte by byte. + + **3** + The device compares a single block against every block + in the range. Only one block is transferred. + .. option:: verify_mode=str : [io_uring_cmd] Specifies the type of command to be used in the verification phase. Defaults to 'read'. diff --git a/engines/bsg.c b/engines/bsg.c index 5d7cac456f..de590fca84 100644 --- a/engines/bsg.c +++ b/engines/bsg.c @@ -104,7 +104,7 @@ void fio_bsg_uring_cmd_init(struct bsg_uring_cmd *cmd, struct bsg_cmd *bc, if (dxfer_dir == SG_DXFER_TO_DEV) { cmd->dout_xferp = (uint64_t)(uintptr_t) io_u->xfer_buf; cmd->dout_xfer_len = io_u->xfer_buflen; - } else { + } else if (dxfer_dir == SG_DXFER_FROM_DEV) { cmd->din_xferp = (uint64_t)(uintptr_t) io_u->xfer_buf; cmd->din_xfer_len = io_u->xfer_buflen; } @@ -155,8 +155,24 @@ static void fio_bsg_varlen_cdb_header(struct bsg_cmd *bc, uint16_t sa) bc->cdb[7] = BSG_VARLEN_CDB_ADDITIONAL_LEN; } +static void fio_bsg_set_verify_bytchk(struct bsg_cmd *bc, unsigned int cdb_len, + unsigned int verify_bytchk) +{ + /* + * BYTCHK occupies bits 2-1 of byte 1 for VERIFY(10)/(16) and byte 10 + * for the 32-byte variable-length VERIFY. BYTCHK=0 leaves the field + * cleared (medium verification only). + */ + if (cdb_len == 32) + bc->cdb[10] |= verify_bytchk << 1; + else + bc->cdb[1] |= verify_bytchk << 1; +} + int fio_bsg_uring_cmd_prep(struct bsg_uring_cmd *cmd, struct io_u *io_u, - struct bsg_cmd *bc, bool fua, unsigned int cdb_len) + struct bsg_cmd *bc, bool fua, unsigned int cdb_len, + enum bsg_write_mode wmode, + unsigned int verify_bytchk) { struct bsg_data *data = FILE_ENG_DATA(io_u->file); unsigned long long offset, nlb; @@ -207,6 +223,39 @@ int fio_bsg_uring_cmd_prep(struct bsg_uring_cmd *cmd, struct io_u *io_u, } break; case DDIR_WRITE: + if (wmode == BSG_WRITE_MODE_VERIFY) { + /* + * VERIFY data direction depends on BYTCHK: + * 0 medium verification only, no host data transfer + * 1 compare the whole transfer against the medium + * 3 compare a single block against the whole range + * BYTCHK 1 and 3 send data to the device. + */ + int dxfer = verify_bytchk ? SG_DXFER_TO_DEV : + SG_DXFER_NONE; + + fio_bsg_uring_cmd_init(cmd, bc, io_u, dxfer, cdb_len); + switch (cdb_len) { + case 10: + bc->cdb[0] = bsg_cmd_verify_10; + break; + case 16: + bc->cdb[0] = bsg_cmd_verify_16; + break; + case 32: + fio_bsg_varlen_cdb_header(bc, BSG_SA_VERIFY_32); + break; + } + fio_bsg_set_verify_bytchk(bc, cdb_len, verify_bytchk); + /* + * BYTCHK=3 compares one block against the entire + * range, so only a single block is transferred while + * the CDB still carries the full block count. + */ + if (verify_bytchk == 3) + cmd->dout_xfer_len = data->bs; + break; + } fio_bsg_uring_cmd_init(cmd, bc, io_u, SG_DXFER_TO_DEV, cdb_len); switch (cdb_len) { case 10: diff --git a/engines/bsg.h b/engines/bsg.h index 9c367e280b..b841b65912 100644 --- a/engines/bsg.h +++ b/engines/bsg.h @@ -54,6 +54,7 @@ struct bsg_uring_cmd { #define BSG_VARLEN_CDB_ADDITIONAL_LEN 0x18 /* 24 -> total 32 bytes */ #define BSG_SA_READ_32 0x0009 #define BSG_SA_WRITE_32 0x000B +#define BSG_SA_VERIFY_32 0x000A enum bsg_io_opcode { bsg_cmd_read_10 = 0x28, @@ -64,9 +65,17 @@ enum bsg_io_opcode { bsg_cmd_unmap = 0x42, bsg_cmd_write_10 = 0x2A, bsg_cmd_write_16 = 0x8A, + bsg_cmd_verify_10 = 0x2F, + bsg_cmd_verify_16 = 0x8F, bsg_cmd_varlen = BSG_VARLEN_CDB_OPCODE, }; +/* Which command mode to issue for DDIR_WRITE ddir. */ +enum bsg_write_mode { + BSG_WRITE_MODE_WRITE = 0, + BSG_WRITE_MODE_VERIFY, +}; + struct bsg_cmd { unsigned char cdb[MAX_CDB_LEN]; unsigned char sb[MAX_SB]; @@ -83,6 +92,8 @@ int fio_bsg_uring_cmd_read_capacity(struct thread_data *td, unsigned int *bs, int fio_bsg_uring_cmd_get_file_size(struct thread_data *td, struct fio_file *f); int fio_bsg_uring_cmd_prep(struct bsg_uring_cmd *cmd, struct io_u *io_u, - struct bsg_cmd *bc, bool fua, unsigned int cdb_len); + struct bsg_cmd *bc, bool fua, unsigned int cdb_len, + enum bsg_write_mode wmode, + unsigned int verify_bytchk); #endif /* FIO_BSG_H */ diff --git a/engines/io_uring.c b/engines/io_uring.c index 6d06fead6a..c27b676f30 100644 --- a/engines/io_uring.c +++ b/engines/io_uring.c @@ -185,6 +185,7 @@ struct ioring_data { /* BSG */ struct bsg_cmd *bc; bool fua[DDIR_RWDIR_CNT]; + enum bsg_write_mode wmode; }; struct ioring_options { @@ -215,6 +216,7 @@ struct ioring_options { char *pi_chk; enum uring_cmd_type cmd_type; unsigned int cdb_len; + unsigned int verify_bytchk; }; static unsigned int enter_flags = IORING_ENTER_GETEVENTS; @@ -559,6 +561,30 @@ static struct fio_option options[] = { .category = FIO_OPT_C_ENGINE, .group = FIO_OPT_G_IOURING, }, + { + .name = "verify_bytchk", + .lname = "SCSI VERIFY BYTCHK field", + .type = FIO_OPT_STR, + .off1 = offsetof(struct ioring_options, verify_bytchk), + .help = "BYTCHK field for write_mode=verify (bsg cmd_type only)", + .def = "0", + .posval = { + { .ival = "0", + .oval = 0, + .help = "Medium verify", + }, + { .ival = "1", + .oval = 1, + .help = "Full compare", + }, + { .ival = "3", + .oval = 3, + .help = "Single-block compare", + }, + }, + .category = FIO_OPT_C_ENGINE, + .group = FIO_OPT_G_IOURING, + }, CMDPRIO_OPTIONS(struct ioring_options, FIO_OPT_G_IOURING), { .name = "md_per_io_size", @@ -877,7 +903,8 @@ static int fio_ioring_cmd_prep(struct thread_data *td, struct io_u *io_u) cmd = (struct bsg_uring_cmd *)sqe->cmd; return fio_bsg_uring_cmd_prep(cmd, io_u, &ld->bc[io_u->index], - ld->fua[io_u->ddir], o->cdb_len); + ld->fua[io_u->ddir], o->cdb_len, + ld->wmode, o->verify_bytchk); } } @@ -1646,10 +1673,31 @@ static int fio_ioring_cmd_init(struct thread_data *td, struct ioring_data *ld) } else if (o->cmd_type == FIO_URING_CMD_BSG) { ld->bc = calloc(td->o.iodepth, sizeof(struct bsg_cmd)); - if (td_write(td) && o->write_mode != FIO_URING_CMD_WMODE_WRITE) { - log_err("Not Support Write mode in BSG io_uring_cmd\n"); - td_verror(td, EINVAL, "fio_ioring_cmd_init"); - return 1; + if (td_write(td)) { + if (o->write_mode != FIO_URING_CMD_WMODE_WRITE && + o->write_mode != FIO_URING_CMD_WMODE_VERIFY) { + log_err("Not Support Write mode in BSG io_uring_cmd\n"); + td_verror(td, EINVAL, "fio_ioring_cmd_init"); + return 1; + } + if (o->write_mode == FIO_URING_CMD_WMODE_VERIFY && + o->writefua) { + log_err("writefua is not supported " + "with write_mode=verify for bsg\n"); + td_verror(td, EINVAL, "fio_ioring_cmd_init"); + return 1; + } + if (o->write_mode != FIO_URING_CMD_WMODE_VERIFY && + o->verify_bytchk != 0) { + log_err("verify_bytchk is only supported " + "with write_mode=verify for bsg\n"); + td_verror(td, EINVAL, "fio_ioring_cmd_init"); + return 1; + } + if (o->write_mode == FIO_URING_CMD_WMODE_VERIFY) + ld->wmode = BSG_WRITE_MODE_VERIFY; + else + ld->wmode = BSG_WRITE_MODE_WRITE; } if (o->readfua) diff --git a/fio.1 b/fio.1 index 4ca69e1647..f112d88ec9 100644 --- a/fio.1 +++ b/fio.1 @@ -2874,7 +2874,10 @@ Use Write Uncorrectable commands for write operations Use Write Zeroes commands for write operations .TP .B verify -Use Verify commands for write operations +Use Verify commands for write operations. Supported for both nvme and bsg +cmd_type. For bsg, the SCSI VERIFY(10)/(16)/(32) opcode is selected by +\fBcdb_len\fR and the data comparison behavior by \fBverify_bytchk\fR; +\fBwritefua\fR is not supported in this mode. .TP .B zone_append Use Zone Append commands for write operations. Requires zonemode=zbd @@ -2887,6 +2890,26 @@ percentage specified. Example: \fBwrite/60:zeroes/30:uncor/10\fR or \fBwrite/50:zeroes/:uncor/\fR .RE .TP +.BI (io_uring_cmd)verify_bytchk \fR=\fPint +BYTCHK field for the SCSI VERIFY command issued when \fBwrite_mode\fR is +verify and \fBcmd_type\fR is bsg. Only valid with write_mode=verify. +Defaults to 0. +.RS +.RS +.TP +.B 0 +Medium verification only; no data is transferred to the device. +.TP +.B 1 +The device compares the full transfer against the data stored on the medium, +byte by byte. +.TP +.B 3 +The device compares a single block against every block in the range. Only one +block is transferred. +.RE +.RE +.TP .BI (io_uring_cmd)verify_mode \fR=\fPstr Specifies the type of command to be used in the verification phase. Defaults to 'read'. .RS From 6394aa3ce95156f6f4e6835bc8f97bb3c57f6b85 Mon Sep 17 00:00:00 2001 From: Jungwon Lee Date: Thu, 2 Jul 2026 10:42:14 +0900 Subject: [PATCH 5/5] engines/io_uring: add read_mode=prefetch for bsg cmd_type Introduce a new engine option, read_mode, that selects which command variant is issued for DDIR_READ, symmetric to the existing write_mode option. Two values are supported: 'read', which preserves the current behavior, and 'prefetch', which issues SCSI PRE-FETCH commands. PRE-FETCH pulls the requested LBAs from the medium into the device read cache without transferring the data to the host. This is useful for warming the device read cache prior to a subsequent measurement, and for measuring the overhead of the prefetch path itself. The PRE-FETCH CDB length follows the cdb_len option: PRE-FETCH(10)=0x34 and PRE-FETCH(16)=0x90. SBC does not define a 32-byte PRE-FETCH service action, so cdb_len=32 is rejected with read_mode=prefetch rather than silently falling back. readfua is also rejected in this mode because the FUA bit is not defined for PRE-FETCH. read_mode is currently only meaningful for the bsg cmd_type. Passing a non-default value with cmd_type=nvme is rejected at engine init rather than silently ignored, so that users notice a misconfiguration. Signed-off-by: Jungwon Lee --- HOWTO.rst | 16 +++++++++++ engines/bsg.c | 20 +++++++++++++- engines/bsg.h | 14 +++++++++- engines/io_uring.c | 66 ++++++++++++++++++++++++++++++++++++++++++++-- fio.1 | 17 ++++++++++++ 5 files changed, 129 insertions(+), 4 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 1b6a0adf8b..410be6d39f 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -3122,6 +3122,22 @@ with the caveat that when used on the command line, they must come after the The device compares a single block against every block in the range. Only one block is transferred. +.. option:: read_mode=str : [io_uring_cmd] + + Specifies the type of read operation. Only supported with + cmd_type=bsg. Defaults to 'read'. + + **read** + Use Read commands for read operations + + **prefetch** + Use SCSI Pre-Fetch commands, which pull the requested + blocks from the medium into the device read cache + without transferring data to the host. Useful for + cache-warming and prefetch-overhead benchmarks. The + Pre-Fetch(10)/(16) opcode is selected by :option:`cdb_len`. + :option:`readfua` is not supported in this mode. + .. option:: verify_mode=str : [io_uring_cmd] Specifies the type of command to be used in the verification phase. Defaults to 'read'. diff --git a/engines/bsg.c b/engines/bsg.c index de590fca84..f0c9f6f02b 100644 --- a/engines/bsg.c +++ b/engines/bsg.c @@ -172,7 +172,8 @@ static void fio_bsg_set_verify_bytchk(struct bsg_cmd *bc, unsigned int cdb_len, int fio_bsg_uring_cmd_prep(struct bsg_uring_cmd *cmd, struct io_u *io_u, struct bsg_cmd *bc, bool fua, unsigned int cdb_len, enum bsg_write_mode wmode, - unsigned int verify_bytchk) + unsigned int verify_bytchk, + enum bsg_read_mode rmode) { struct bsg_data *data = FILE_ENG_DATA(io_u->file); unsigned long long offset, nlb; @@ -203,6 +204,23 @@ int fio_bsg_uring_cmd_prep(struct bsg_uring_cmd *cmd, struct io_u *io_u, switch (io_u->ddir) { case DDIR_READ: + if (rmode == BSG_READ_MODE_PREFETCH) { + /* + * PREFETCH pulls the requested blocks from the medium + * into the device read cache. No host data transfer. + */ + fio_bsg_uring_cmd_init(cmd, bc, io_u, SG_DXFER_NONE, + cdb_len); + switch (cdb_len) { + case 10: + bc->cdb[0] = bsg_cmd_prefetch_10; + break; + case 16: + bc->cdb[0] = bsg_cmd_prefetch_16; + break; + } + break; + } fio_bsg_uring_cmd_init(cmd, bc, io_u, SG_DXFER_FROM_DEV, cdb_len); switch (cdb_len) { case 10: diff --git a/engines/bsg.h b/engines/bsg.h index b841b65912..725a391e19 100644 --- a/engines/bsg.h +++ b/engines/bsg.h @@ -56,6 +56,9 @@ struct bsg_uring_cmd { #define BSG_SA_WRITE_32 0x000B #define BSG_SA_VERIFY_32 0x000A +/* SAM status byte returned in bits [0..7] of the BSG uring_cmd big_cqe[0] */ +#define BSG_STAT_CONDITION_MET 0x04 + enum bsg_io_opcode { bsg_cmd_read_10 = 0x28, bsg_cmd_read_16 = 0x88, @@ -67,6 +70,8 @@ enum bsg_io_opcode { bsg_cmd_write_16 = 0x8A, bsg_cmd_verify_10 = 0x2F, bsg_cmd_verify_16 = 0x8F, + bsg_cmd_prefetch_10 = 0x34, + bsg_cmd_prefetch_16 = 0x90, bsg_cmd_varlen = BSG_VARLEN_CDB_OPCODE, }; @@ -76,6 +81,12 @@ enum bsg_write_mode { BSG_WRITE_MODE_VERIFY, }; +/* Which command mode to issue for DDIR_READ ddir. */ +enum bsg_read_mode { + BSG_READ_MODE_READ = 0, + BSG_READ_MODE_PREFETCH, +}; + struct bsg_cmd { unsigned char cdb[MAX_CDB_LEN]; unsigned char sb[MAX_SB]; @@ -94,6 +105,7 @@ int fio_bsg_uring_cmd_get_file_size(struct thread_data *td, struct fio_file *f); int fio_bsg_uring_cmd_prep(struct bsg_uring_cmd *cmd, struct io_u *io_u, struct bsg_cmd *bc, bool fua, unsigned int cdb_len, enum bsg_write_mode wmode, - unsigned int verify_bytchk); + unsigned int verify_bytchk, + enum bsg_read_mode rmode); #endif /* FIO_BSG_H */ diff --git a/engines/io_uring.c b/engines/io_uring.c index c27b676f30..f1e0527ca9 100644 --- a/engines/io_uring.c +++ b/engines/io_uring.c @@ -121,6 +121,11 @@ enum uring_cmd_verify_mode { FIO_URING_CMD_VMODE_COMPARE, }; +enum uring_cmd_read_mode { + FIO_URING_CMD_RMODE_READ = 0, + FIO_URING_CMD_RMODE_PREFETCH, +}; + struct io_sq_ring { unsigned *head; unsigned *tail; @@ -186,6 +191,7 @@ struct ioring_data { struct bsg_cmd *bc; bool fua[DDIR_RWDIR_CNT]; enum bsg_write_mode wmode; + enum bsg_read_mode rmode; }; struct ioring_options { @@ -217,6 +223,7 @@ struct ioring_options { enum uring_cmd_type cmd_type; unsigned int cdb_len; unsigned int verify_bytchk; + unsigned int read_mode; }; static unsigned int enter_flags = IORING_ENTER_GETEVENTS; @@ -585,6 +592,26 @@ static struct fio_option options[] = { .category = FIO_OPT_C_ENGINE, .group = FIO_OPT_G_IOURING, }, + { + .name = "read_mode", + .lname = "Read command type", + .type = FIO_OPT_STR, + .off1 = offsetof(struct ioring_options, read_mode), + .help = "Specify the read operation type (bsg cmd_type only)", + .def = "read", + .posval = { + { .ival = "read", + .oval = FIO_URING_CMD_RMODE_READ, + .help = "Use Read commands for read operations", + }, + { .ival = "prefetch", + .oval = FIO_URING_CMD_RMODE_PREFETCH, + .help = "Use Pre-Fetch commands for read operations", + }, + }, + .category = FIO_OPT_C_ENGINE, + .group = FIO_OPT_G_IOURING, + }, CMDPRIO_OPTIONS(struct ioring_options, FIO_OPT_G_IOURING), { .name = "md_per_io_size", @@ -904,7 +931,8 @@ static int fio_ioring_cmd_prep(struct thread_data *td, struct io_u *io_u) return fio_bsg_uring_cmd_prep(cmd, io_u, &ld->bc[io_u->index], ld->fua[io_u->ddir], o->cdb_len, - ld->wmode, o->verify_bytchk); + ld->wmode, o->verify_bytchk, + ld->rmode); } } @@ -990,8 +1018,17 @@ static struct io_u *fio_ioring_cmd_event(struct thread_data *td, int event) } } else if (o->cmd_type == FIO_URING_CMD_BSG) { ret = cqe->big_cqe[0]; - if (ret) + if (ret) { + /* + * PRE-FETCH completes successfully with SCSI status + * CONDITION_MET rather than GOOD; don't treat it as an error. + */ + if (ld->rmode == BSG_READ_MODE_PREFETCH && + io_u->ddir == DDIR_READ && + (ret & 0xff) == BSG_STAT_CONDITION_MET) + ret &= ~0xff; io_u->error = ret; + } } ret: @@ -1627,6 +1664,11 @@ static int fio_ioring_cmd_init(struct thread_data *td, struct ioring_data *ld) } if (o->cmd_type == FIO_URING_CMD_NVME) { + if (o->read_mode != FIO_URING_CMD_RMODE_READ) { + log_err("fio: read_mode is only supported with " + "cmd_type=bsg\n"); + return 1; + } if (td_write(td)) { if (o->wmode_split_nr > 1) { int i; @@ -1699,6 +1741,26 @@ static int fio_ioring_cmd_init(struct thread_data *td, struct ioring_data *ld) else ld->wmode = BSG_WRITE_MODE_WRITE; } + if (td_read(td)) { + if (o->read_mode == FIO_URING_CMD_RMODE_PREFETCH && + o->readfua) { + log_err("readfua is not supported " + "with read_mode=prefetch for bsg\n"); + td_verror(td, EINVAL, "fio_ioring_cmd_init"); + return 1; + } + if (o->read_mode == FIO_URING_CMD_RMODE_PREFETCH && + o->cdb_len == 32) { + log_err("cdb_len=32 is not supported " + "with read_mode=prefetch for bsg\n"); + td_verror(td, EINVAL, "fio_ioring_cmd_init"); + return 1; + } + if (o->read_mode == FIO_URING_CMD_RMODE_PREFETCH) + ld->rmode = BSG_READ_MODE_PREFETCH; + else + ld->rmode = BSG_READ_MODE_READ; + } if (o->readfua) ld->fua[DDIR_READ] = 1; diff --git a/fio.1 b/fio.1 index f112d88ec9..915c840733 100644 --- a/fio.1 +++ b/fio.1 @@ -2910,6 +2910,23 @@ block is transferred. .RE .RE .TP +.BI (io_uring_cmd)read_mode \fR=\fPstr +Specifies the type of read operation. Only supported with cmd_type=bsg. +Defaults to 'read'. +.RS +.RS +.TP +.B read +Use Read commands for read operations +.TP +.B prefetch +Use SCSI Pre-Fetch commands, which pull the requested blocks from the medium +into the device read cache without transferring data to the host. Useful for +cache-warming and prefetch-overhead benchmarks. The Pre-Fetch(10)/(16) opcode +is selected by \fBcdb_len\fR. \fBreadfua\fR is not supported in this mode. +.RE +.RE +.TP .BI (io_uring_cmd)verify_mode \fR=\fPstr Specifies the type of command to be used in the verification phase. Defaults to 'read'. .RS