From 9a7e23bf287c16e65e9f1dbf87b838bdcc5b9ce2 Mon Sep 17 00:00:00 2001 From: Broc Going Date: Thu, 2 Jul 2026 13:04:50 -0700 Subject: [PATCH] nvme_test: Update run_ns_io to be platform agnostic. Instead of using linux-specific commands to run reads and writes, run_ns_io now uses nvme commands that will work regardless of platform. Pulls some functionality from nvme_test_io into nvme_test to allow for code reuse and to avoid code duplication when detecting LBA IO parameters and configuring read and write commands. Signed-off-by: Broc Going --- tests/nvme_test.py | 126 +++++++++++++++++++++++++++++++++++++++--- tests/nvme_test_io.py | 72 +++++------------------- 2 files changed, 131 insertions(+), 67 deletions(-) diff --git a/tests/nvme_test.py b/tests/nvme_test.py index f48493943b..118bd0c1f0 100644 --- a/tests/nvme_test.py +++ b/tests/nvme_test.py @@ -666,6 +666,87 @@ def get_error_log(self): return 0 if err_log_entry_count == entry_count else 1 + def _get_rw_io_params_per_lba(self, lba_size=None, update_cache=False): + """Return (ms, prinfo, data_size_per_lba). + + ms: metadata size per LBA. + prinfo: NVMe Protection Information setting (0 or 8). + data_size_per_lba: data payload per LBA (block_size + metadata if extended). + + Args: + lba_size: Optional LBA data size in bytes. If 0 or None, uses + namespace's default LBA data size. + update_cache: If True, refresh cached namespace parameters + from the device. Defaults to False. + + PI type occupies bits 2:0 of the DPS field; bits 5:3 are PIF. + """ + # Update cached values in case they were changed by a previous action. + if update_cache: + self.ns_dps = self._get_ns_dps() + self.ns_meta_ext = self._is_metadata_ext() + self.flbas = self._get_active_lbaf_index() + (ds, ms) = self.get_lba_format_size() + + # Determine block size from provided size, or use namespace default. + block_size = ds if lba_size in (None, 0) else int(lba_size) + + pi_type = self.ns_dps & 0x7 + + if pi_type != 0 and ms != 0 and self.ns_meta_ext: + # PI active + extended LBA (metadata appended to data buffer). + # Use PRACT=1 (--prinfo=8) so the controller inserts and strips PI + # automatically. With PRACT=1 the PI bytes are not transferred + # over the host interface, so data_size equals the logical block + # data size only (block_size), not block_size+ms. This works for all PI sizes + # (8 bytes for PIF 0/2, 16 bytes for PIF 1) and all guard widths + # (16-bit, 32-bit, 64-bit CRC) because the controller handles + # the PI entirely. + prinfo = 8 + data_size = block_size + elif pi_type != 0 and ms != 0 and not self.ns_meta_ext: + # PI active + separate metadata (flbas bit 4 clear). PRACT=1 + # (--prinfo=8) is invalid for the Compare command on this format + # (NVMe spec: PRACT=1 for Compare requires PI in the host data + # buffer, which only applies to the extended-LBA layout). Use + # prinfo=0 (PRACT=0, PRCHK=0) for all operations and supply an + # explicit zero-filled metadata buffer of ms bytes so that the + # stored metadata and the compared metadata are both known zeros. + # PRCHK=0 skips PI validation, so the zero PI bytes are accepted + # by the controller on write and matched exactly on compare. This + # is PI-format and guard-width agnostic: the entire ms-byte + # metadata slot (whether holding an 8-byte PI with 16-bit or + # 32-bit guard, or a 16-byte PI with 64-bit guard) is zeroed. + prinfo = 0 + data_size = block_size + else: + # No PI. For extended LBA format (metadata appended to the data + # buffer) include the metadata bytes so that the controller sees + # a consistent data+metadata unit. For separate metadata format + # (flbas bit 4 clear) metadata is transferred via a different + # pointer and must NOT be folded into the data buffer; use block_size only + # so that the data transfer length matches exactly one LBA. + prinfo = 0 + data_size = block_size + ms if self.ns_meta_ext else block_size + + return ms, prinfo, data_size + + def _build_nvme_rw_cmd(self, opcode, ns_path, start_block, block_count, + data_size, data_file, prinfo=0, + metadata_size=0, metadata_file=None): + """Build nvme read/write command with optional PI/metadata options.""" + cmd = f'{self.nvme_bin} {opcode} {ns_path} ' + \ + f'--start-block={str(start_block)} ' + \ + f'--block-count={str(block_count)} ' + \ + f'--data-size={str(data_size)} --data="{data_file}"' + + if prinfo: + cmd += f" --prinfo={prinfo}" + if metadata_size > 0 and metadata_file is not None: + cmd += f' --metadata-size={metadata_size} --metadata="{metadata_file}"' + + return cmd + def run_ns_io(self, nsid, lbads, count=10): """ Wrapper to run ios on namespace under test. - Args: @@ -673,12 +754,39 @@ def run_ns_io(self, nsid, lbads, count=10): - Returns: - None """ - (ds, _) = self.get_lba_format_size() - block_size = ds if int(lbads) < 9 else 2 ** int(lbads) - ns_path = self.ctrl + "n" + str(nsid) - io_cmd = "dd if=" + ns_path + " of=/dev/null" + " bs=" + \ - str(block_size) + " count=" + str(count) - self.assertEqual(self.run_cmd(io_cmd).returncode, 0) - io_cmd = "dd if=/dev/zero of=" + ns_path + " bs=" + \ - str(block_size) + " count=" + str(count) - self.assertEqual(self.run_cmd(io_cmd).returncode, 0) + count = int(count) + lbads = 0 if lbads is None else int(lbads) + lba_size = 0 if lbads < 9 else 2 ** lbads + ms, prinfo, data_size_per_lba = self._get_rw_io_params_per_lba(lba_size, True) + ns_path = self.ns1 if int(nsid) == int(self.default_nsid) else self.ctrl + "n" + str(nsid) + block_count = count - 1 + data_size = data_size_per_lba * count + metadata_size = ms * count if ms > 0 and not self.ns_meta_ext else 0 + + log_root = self.test_log_dir if self.test_log_dir != "XXX" else self.log_dir + if not os.path.exists(log_root): + os.makedirs(log_root) + + write_file = os.path.join(log_root, f"run_ns_io_write_{nsid}.bin") + read_file = os.path.join(log_root, f"run_ns_io_read_{nsid}.bin") + write_meta_file = os.path.join(log_root, f"run_ns_io_write_meta_{nsid}.bin") + read_meta_file = os.path.join(log_root, f"run_ns_io_read_meta_{nsid}.bin") + + with open(write_file, "wb") as out_file: + out_file.write(bytes(data_size)) + open(read_file, 'a').close() + + if metadata_size > 0: + with open(write_meta_file, "wb") as meta_out: + meta_out.write(bytes(metadata_size)) + open(read_meta_file, 'a').close() + + read_cmd = self._build_nvme_rw_cmd("read", ns_path, 0, block_count, + data_size, read_file, prinfo, + metadata_size, read_meta_file) + self.assertEqual(self.run_cmd(read_cmd).returncode, 0) + + write_cmd = self._build_nvme_rw_cmd("write", ns_path, 0, block_count, + data_size, write_file, prinfo, + metadata_size, write_meta_file) + self.assertEqual(self.run_cmd(write_cmd).returncode, 0) diff --git a/tests/nvme_test_io.py b/tests/nvme_test_io.py index d1fc62ed30..328f5f4268 100644 --- a/tests/nvme_test_io.py +++ b/tests/nvme_test_io.py @@ -51,46 +51,7 @@ def _init_io_params(self): to a different LBA format so that the parameters match the new format. """ - # common code used in various testcases. - (ds, ms) = self.get_lba_format_size() - self.ms = ms - # PI type occupies bits 2:0 of the DPS field; bits 5:3 are PIF. - pi_type = self.ns_dps & 0x7 - if pi_type != 0 and ms != 0 and self.ns_meta_ext: - # PI active + extended LBA (metadata appended to data buffer). - # Use PRACT=1 (--prinfo=8) so the controller inserts and strips PI - # automatically. With PRACT=1 the PI bytes are not transferred - # over the host interface, so data_size equals the logical block - # data size only (ds), not ds+ms. This works for all PI sizes - # (8 bytes for PIF 0/2, 16 bytes for PIF 1) and all guard widths - # (16-bit, 32-bit, 64-bit CRC) because the controller handles - # the PI entirely. - self.prinfo = 8 - self.data_size = ds - elif pi_type != 0 and ms != 0 and not self.ns_meta_ext: - # PI active + separate metadata (flbas bit 4 clear). PRACT=1 - # (--prinfo=8) is invalid for the Compare command on this format - # (NVMe spec: PRACT=1 for Compare requires PI in the host data - # buffer, which only applies to the extended-LBA layout). Use - # prinfo=0 (PRACT=0, PRCHK=0) for all operations and supply an - # explicit zero-filled metadata buffer of ms bytes so that the - # stored metadata and the compared metadata are both known zeros. - # PRCHK=0 skips PI validation, so the zero PI bytes are accepted - # by the controller on write and matched exactly on compare. This - # is PI-format and guard-width agnostic: the entire ms-byte - # metadata slot (whether holding an 8-byte PI with 16-bit or - # 32-bit guard, or a 16-byte PI with 64-bit guard) is zeroed. - self.prinfo = 0 - self.data_size = ds - else: - # No PI. For extended LBA format (metadata appended to the data - # buffer) include the metadata bytes so that the controller sees - # a consistent data+metadata unit. For separate metadata format - # (flbas bit 4 clear) the metadata is transferred via a different - # pointer and must NOT be folded into the data buffer; use ds only - # so that the data transfer length matches exactly one LBA. - self.prinfo = 0 - self.data_size = ds + ms if self.ns_meta_ext else ds + self.ms, self.prinfo, self.data_size = self._get_rw_io_params_per_lba() self.start_block = 0 self.block_count = 0 self.write_file = "write_file.txt" @@ -100,6 +61,9 @@ def _init_io_params(self): if self.ms > 0 and not self.ns_meta_ext: self.write_meta_file = "write_meta_file.bin" self.read_meta_file = "read_meta_file.bin" + else: + self.write_meta_file = None + self.read_meta_file = None def tearDown(self): """ Post Section for TestNVMeIO """ @@ -141,15 +105,11 @@ def nvme_write(self): - Returns: - return code for nvme write command. """ - write_cmd = f"{self.nvme_bin} write {self.ns1} " + \ - f"--start-block={str(self.start_block)} " + \ - f"--block-count={str(self.block_count)} " + \ - f"--data-size={str(self.data_size)} --data={self.write_file}" - if self.prinfo: - write_cmd += f" --prinfo={self.prinfo}" - if self.ms > 0 and not self.ns_meta_ext: - write_cmd += \ - f" --metadata-size={self.ms} --metadata={self.write_meta_file}" + metadata_size = self.ms if self.ms > 0 and not self.ns_meta_ext else 0 + write_cmd = self._build_nvme_rw_cmd("write", self.ns1, self.start_block, + self.block_count,self.data_size, + self.write_file, self.prinfo, + metadata_size, self.write_meta_file) return self.exec_cmd(write_cmd) def nvme_read(self): @@ -159,13 +119,9 @@ def nvme_read(self): - Returns: - return code for nvme read command. """ - read_cmd = f"{self.nvme_bin} read {self.ns1} " + \ - f"--start-block={str(self.start_block)} " + \ - f"--block-count={str(self.block_count)} " + \ - f"--data-size={str(self.data_size)} --data={self.read_file}" - if self.prinfo: - read_cmd += f" --prinfo={self.prinfo}" - if self.ms > 0 and not self.ns_meta_ext: - read_cmd += \ - f" --metadata-size={self.ms} --metadata={self.read_meta_file}" + metadata_size = self.ms if self.ms > 0 and not self.ns_meta_ext else 0 + read_cmd = self._build_nvme_rw_cmd("read", self.ns1, self.start_block, + self.block_count, self.data_size, + self.read_file, self.prinfo, + metadata_size, self.read_meta_file) return self.exec_cmd(read_cmd)