diff --git a/Documentation/nvme-copy.txt b/Documentation/nvme-copy.txt index 1fad952d65..0785474cb9 100644 --- a/Documentation/nvme-copy.txt +++ b/Documentation/nvme-copy.txt @@ -11,6 +11,8 @@ SYNOPSIS 'nvme-copy' [--sdlba= | -d ] [--blocks= | -b ] [--slbs= | -s ] + [--snsids= | -N ] + [--sopts= | -O ] [--limited-retry | -l] [--force-unit-access | -f] [--prinfow= | -p ] @@ -44,6 +46,14 @@ OPTIONS -s :: Comma separated list of the starting blocks in each range +--snsids=:: +-N :: + Comma separated list of the source namespace identifiers in each range + +--sopts=:: +-O :: + Comma separated list of the source options in each range + --limited-retry:: -l:: Sets the limited retry flag. diff --git a/libnvme-wrap.c b/libnvme-wrap.c index 354e7f7afa..a62d60c012 100644 --- a/libnvme-wrap.c +++ b/libnvme-wrap.c @@ -42,6 +42,18 @@ VOID_FN(nvme_init_copy_range_f1, __u32 *elbats, __u16 nr), ARGS(copy, nlbs, slbas, eilbrts, elbatms, elbats, nr)) +VOID_FN(nvme_init_copy_range_f2, + PROTO(struct nvme_copy_range_f2 *copy, __u32 *snsids, + __u16 *nlbs, __u64 *slbas, __u16 *sopts, __u32 *eilbrts, + __u32 *elbatms, __u32 *elbats, __u16 nr), + ARGS(copy, snsids, nlbs, slbas, sopts, eilbrts, elbatms, elbats, nr)) + +VOID_FN(nvme_init_copy_range_f3, + PROTO(struct nvme_copy_range_f3 *copy, __u32 *snsids, + __u16 *nlbs, __u64 *slbas, __u16 *sopts, __u64 *eilbrts, + __u32 *elbatms, __u32 *elbats, __u16 nr), + ARGS(copy, snsids, nlbs, slbas, sopts, eilbrts, elbatms, elbats, nr)) + FN(nvme_get_feature_length2, int, PROTO(int fid, __u32 cdw11, enum nvme_data_tfr dir, diff --git a/nvme-print-stdout.c b/nvme-print-stdout.c index b97e4833a6..b990f623e5 100644 --- a/nvme-print-stdout.c +++ b/nvme-print-stdout.c @@ -2167,7 +2167,9 @@ static void stdout_id_ctrl_cqes(__u8 cqes) static void stdout_id_ctrl_oncs(__le16 ctrl_oncs) { __u16 oncs = le16_to_cpu(ctrl_oncs); - __u16 rsvd = (oncs & 0xFE00) >> 9; + __u16 rsvd = (oncs & 0xF800) >> 11; + __u16 afc = (oncs & 0x400) >> 10; + __u16 csa = (oncs & 0x200) >> 9; __u16 copy = (oncs & 0x100) >> 8; __u16 vrfy = (oncs & 0x80) >> 7; __u16 tmst = (oncs & 0x40) >> 6; @@ -2179,7 +2181,11 @@ static void stdout_id_ctrl_oncs(__le16 ctrl_oncs) __u16 cmp = oncs & 0x1; if (rsvd) - printf(" [15:9] : %#x\tReserved\n", rsvd); + printf(" [15:11] : %#x\tReserved\n", rsvd); + printf(" [10:10] : %#x\tAll Fast Copy %sSupported\n", + afc, afc ? "" : "Not "); + printf(" [9:9] : %#x\tCopy Single Atomicity %sSupported\n", + csa, csa ? "" : "Not "); printf(" [8:8] : %#x\tCopy %sSupported\n", copy, copy ? "" : "Not "); printf(" [7:7] : %#x\tVerify %sSupported\n", @@ -2289,10 +2295,16 @@ static void stdout_id_ctrl_ocfs(__le16 ctrl_ocfs) { __u16 ocfs = le16_to_cpu(ctrl_ocfs); __u16 rsvd = (ocfs & 0xfffc) >> 2; + __u8 copy_fmt_3 = (ocfs >> 3) & 0x1; + __u8 copy_fmt_2 = (ocfs >> 2) & 0x1; __u8 copy_fmt_1 = (ocfs >> 1) & 0x1; __u8 copy_fmt_0 = ocfs & 0x1; if (rsvd) - printf(" [15:2] : %#x\tReserved\n", rsvd); + printf(" [15:4] : %#x\tReserved\n", rsvd); + printf(" [3:3] : %#x\tController Copy Format 3h %sSupported\n", + copy_fmt_3, copy_fmt_3 ? "" : "Not "); + printf(" [2:2] : %#x\tController Copy Format 2h %sSupported\n", + copy_fmt_2, copy_fmt_2 ? "" : "Not "); printf(" [1:1] : %#x\tController Copy Format 1h %sSupported\n", copy_fmt_1, copy_fmt_1 ? "" : "Not "); printf(" [0:0] : %#x\tController Copy Format 0h %sSupported\n", @@ -4474,8 +4486,13 @@ static void stdout_feature_show_fields(enum nvme_features_id fid, stdout_lba_status_info(result); break; case NVME_FEAT_FID_HOST_BEHAVIOR: - if (buf) + if (buf) { printf("\tHost Behavior Support: %s\n", (buf[0] & 0x1) ? "True" : "False"); + printf("\tCopy Descriptor Format 2h Enabled: %s\n", + (buf[4] & 0x4) ? "True" : "False"); + printf("\tCopy Descriptor Format 3h Enabled: %s\n", + (buf[4] & 0x8) ? "True" : "False"); + } break; case NVME_FEAT_FID_SANITIZE: printf("\tNo-Deallocate Response Mode (NODRM) : %u\n", result & 0x1); diff --git a/nvme.c b/nvme.c index a3fa4d0f82..0df38811fa 100644 --- a/nvme.c +++ b/nvme.c @@ -6558,6 +6558,8 @@ static int copy_cmd(int argc, char **argv, struct command *cmd, struct plugin *p const char *d_sdlba = "64-bit addr of first destination logical block"; const char *d_slbas = "64-bit addr of first block per range (comma-separated list)"; const char *d_nlbs = "number of blocks per range (comma-separated list, zeroes-based values)"; + const char *d_snsids = "source namespace identifier per range (comma-separated list)"; + const char *d_sopts = "source options per range (comma-separated list)"; const char *d_lr = "limited retry"; const char *d_fua = "force unit access"; const char *d_prinfor = "protection information and check field (read part)"; @@ -6573,14 +6575,18 @@ static int copy_cmd(int argc, char **argv, struct command *cmd, struct plugin *p const char *d_format = "source range entry format"; _cleanup_nvme_dev_ struct nvme_dev *dev = NULL; - uint16_t nr, nb, ns, nrts, natms, nats; + uint16_t nr, nb, ns, nrts, natms, nats, nid; __u16 nlbs[128] = { 0 }; __u64 slbas[128] = { 0 }; + __u32 snsids[128] = { 0 }; + __u16 sopts[128] = { 0 }; int err; union { __u32 f0[128]; __u64 f1[101]; + __u32 f2[128]; + __u64 f3[101]; } eilbrts; __u32 elbatms[128] = { 0 }; @@ -6589,6 +6595,8 @@ static int copy_cmd(int argc, char **argv, struct command *cmd, struct plugin *p union { struct nvme_copy_range f0[128]; struct nvme_copy_range_f1 f1[101]; + struct nvme_copy_range_f2 f2[128]; + struct nvme_copy_range_f3 f3[101]; } *copy; struct config { @@ -6596,6 +6604,8 @@ static int copy_cmd(int argc, char **argv, struct command *cmd, struct plugin *p __u64 sdlba; char *slbas; char *nlbs; + char *snsids; + char *sopts; bool lr; bool fua; __u8 prinfow; @@ -6616,6 +6626,8 @@ static int copy_cmd(int argc, char **argv, struct command *cmd, struct plugin *p .sdlba = 0, .slbas = "", .nlbs = "", + .snsids = "", + .sopts = "", .lr = false, .fua = false, .prinfow = 0, @@ -6636,6 +6648,8 @@ static int copy_cmd(int argc, char **argv, struct command *cmd, struct plugin *p OPT_SUFFIX("sdlba", 'd', &cfg.sdlba, d_sdlba), OPT_LIST("slbs", 's', &cfg.slbas, d_slbas), OPT_LIST("blocks", 'b', &cfg.nlbs, d_nlbs), + OPT_LIST("snsids", 'N', &cfg.snsids, d_snsids), + OPT_LIST("sopts", 'O', &cfg.sopts, d_sopts), OPT_FLAG("limited-retry", 'l', &cfg.lr, d_lr), OPT_FLAG("force-unit-access", 'f', &cfg.fua, d_fua), OPT_BYTE("prinfow", 'p', &cfg.prinfow, d_prinfow), @@ -6656,6 +6670,8 @@ static int copy_cmd(int argc, char **argv, struct command *cmd, struct plugin *p nb = argconfig_parse_comma_sep_array_u16(cfg.nlbs, nlbs, ARRAY_SIZE(nlbs)); ns = argconfig_parse_comma_sep_array_u64(cfg.slbas, slbas, ARRAY_SIZE(slbas)); + nid = argconfig_parse_comma_sep_array_u32(cfg.snsids, snsids, ARRAY_SIZE(snsids)); + argconfig_parse_comma_sep_array_u16(cfg.sopts, sopts, ARRAY_SIZE(sopts)); if (cfg.format == 0) { nrts = argconfig_parse_comma_sep_array_u32(cfg.eilbrts, eilbrts.f0, @@ -6663,6 +6679,12 @@ static int copy_cmd(int argc, char **argv, struct command *cmd, struct plugin *p } else if (cfg.format == 1) { nrts = argconfig_parse_comma_sep_array_u64(cfg.eilbrts, eilbrts.f1, ARRAY_SIZE(eilbrts.f1)); + } else if (cfg.format == 2) { + nrts = argconfig_parse_comma_sep_array_u32(cfg.eilbrts, eilbrts.f2, + ARRAY_SIZE(eilbrts.f2)); + } else if (cfg.format == 3) { + nrts = argconfig_parse_comma_sep_array_u64(cfg.eilbrts, eilbrts.f3, + ARRAY_SIZE(eilbrts.f3)); } else { nvme_show_error("invalid format"); return -EINVAL; @@ -6672,6 +6694,15 @@ static int copy_cmd(int argc, char **argv, struct command *cmd, struct plugin *p nats = argconfig_parse_comma_sep_array_u32(cfg.elbats, elbats, ARRAY_SIZE(elbats)); nr = max(nb, max(ns, max(nrts, max(natms, nats)))); + if (cfg.format == 2 || cfg.format == 3) { + if (nr != nid) { + nvme_show_error("formats 2 and 3 require source namespace ids for each source range"); + err = -EINVAL; + } + } else if (nid > 0) { + nvme_show_error("formats 0 and 1 do not support cross-namespace copy"); + err = -EINVAL; + } if (!nr || nr > 128 || (cfg.format == 1 && nr > 101)) { nvme_show_error("invalid range"); return -EINVAL; @@ -6693,6 +6724,12 @@ static int copy_cmd(int argc, char **argv, struct command *cmd, struct plugin *p nvme_init_copy_range(copy->f0, nlbs, slbas, eilbrts.f0, elbatms, elbats, nr); else if (cfg.format == 1) nvme_init_copy_range_f1(copy->f1, nlbs, slbas, eilbrts.f1, elbatms, elbats, nr); + else if (cfg.format == 2) + nvme_init_copy_range_f2(copy->f2, snsids, nlbs, slbas, sopts, eilbrts.f2, elbatms, + elbats, nr); + else if (cfg.format == 3) + nvme_init_copy_range_f3(copy->f3, snsids, nlbs, slbas, sopts, eilbrts.f3, elbatms, + elbats, nr); struct nvme_copy_args args = { .args_size = sizeof(args), diff --git a/tests/nvme_copy_test.py b/tests/nvme_copy_test.py index d84a6d243a..8e03a20441 100644 --- a/tests/nvme_copy_test.py +++ b/tests/nvme_copy_test.py @@ -4,56 +4,124 @@ # # Copyright (c) 2022 Samsung Electronics Co., Ltd. All Rights Reserved. # -# Author: Arunpandian J +# Authors: Arunpandian J +# Joy Gu """ NVMe Copy Testcase:- 1. Issue copy command on set of block; shall pass. + 2. If cross-namespace copy formats are supported, enable and test + cross-namespace copy formats. """ +import subprocess + from nvme_test import TestNVMe class TestNVMeCopy(TestNVMe): """ - Represents NVMe Verify testcase. + Represents NVMe Copy testcase. - Attributes: - - start_block : starting block of to verify operation. - - range : Range of blocks for DSM operation. - - slbs : 64-bit addr of first block per range + - ocfs : optional copy formats supported + - host_behavior_data : host behavior support data to restore during teardown - test_log_dir : directory for logs, temp files. """ def setUp(self): """ Pre Section for TestNVMeCopy """ super().setUp() - self.start_block = 0 - self.range = 1 - self.slbs = 1 - self.namespace = 1 + print("\nSetting up test...") + self.ocfs = self.get_ocfs() + cross_namespace_copy = self.ocfs & 0xc + if cross_namespace_copy: + # get host behavior support data + get_features_cmd = "nvme get-feature " + self.ctrl + \ + " --feature-id=0x16 -l 512 -b" + print("Running command:", get_features_cmd) + proc = subprocess.Popen(get_features_cmd, + shell=True, + stdout=subprocess.PIPE) + self.host_behavior_data = proc.stdout.read() + # enable cross-namespace copy formats + data = list(self.host_behavior_data) + if data[4] & cross_namespace_copy == cross_namespace_copy: + # skip if already enabled + print("Cross-namespace copy already enabled, skipping set-features") + self.host_behavior_data = None + else: + data[4] = cross_namespace_copy + data = bytes(data) + set_features_cmd = "nvme set-feature " + self.ctrl + \ + " --feature-id=0x16 -l 512" + print("Running command:", set_features_cmd) + proc = subprocess.Popen(set_features_cmd, + shell=True, + stdout=subprocess.PIPE, + stdin=subprocess.PIPE) + proc.communicate(input=data) + get_ns_id_cmd = f"nvme get-ns-id {self.ns1}" + print("Running command:", get_ns_id_cmd) + proc = subprocess.Popen(get_ns_id_cmd, shell=True, stdout=subprocess.PIPE) + self.ns1_nsid = int(proc.stdout.read().decode().strip().split(':')[-1]) self.setup_log_dir(self.__class__.__name__) def tearDown(self): """ Post Section for TestNVMeCopy """ + print("Tearing down test...") + if self.host_behavior_data: + # restore saved host behavior support data + set_features_cmd = "nvme set-feature " + self.ctrl + \ + " --feature-id=0x16 -l 512" + print("Running command:", set_features_cmd) + proc = subprocess.Popen(set_features_cmd, + shell=True, + stdout=subprocess.PIPE, + stdin=subprocess.PIPE) + proc.communicate(input=self.host_behavior_data) super().tearDown() - def copy(self): + def copy(self, sdlba, blocks, slbs, **kwargs): """ Wrapper for nvme copy - Args: - - None + - sdlba : destination logical block address + - blocks : number of logical blocks (0-based) + - slbs : source range logical block address + - descriptor_format : copy descriptor format (optional) + - snsids : source namespace id (optional) + - sopts : source options (optional) - Returns: - - return code for nvme copy command. + - None """ - copy_cmd = "nvme copy " + self.ctrl + \ - " --namespace-id=" + str(self.namespace) + \ - " --sdlba=" + str(self.start_block) + \ - " --blocks=" + str(self.range) + \ - " --slbs=" + str(self.range) - return self.exec_cmd(copy_cmd) + # skip if descriptor format not supported (default format is 0) + desc_format = kwargs.get("descriptor_format", 0) + if not self.ocfs & (1 << desc_format): + print(f"Skip copy because descriptor format {desc_format} is not supported") + return + # build copy command + copy_cmd = "nvme copy " + self.ns1 + \ + " --sdlba=" + str(sdlba) + \ + " --blocks=" + str(blocks) + \ + " --slbs=" + str(slbs) + if kwargs.get("descriptor_format") is not None: + copy_cmd += " --format=" + str(kwargs.get("descriptor_format")) + if kwargs.get("snsids") is not None: + copy_cmd += " --snsids=" + str(kwargs.get("snsids")) + if kwargs.get("sopts") is not None: + copy_cmd += " --sopts=" + str(kwargs.get("sopts")) + # run and assert success + print("Running command:", copy_cmd) + self.assertEqual(self.exec_cmd(copy_cmd), 0) def test_copy(self): """ Testcase main """ - self.assertEqual(self.copy(), 0) + print("Running test...") + self.copy(0, 1, 2, descriptor_format=0) + self.copy(0, 1, 2, descriptor_format=1) + self.copy(0, 1, 2, descriptor_format=2, snsids=self.ns1_nsid) + self.copy(0, 1, 2, descriptor_format=2, snsids=self.ns1_nsid, sopts=0) + self.copy(0, 1, 2, descriptor_format=3, snsids=self.ns1_nsid) + self.copy(0, 1, 2, descriptor_format=3, snsids=self.ns1_nsid, sopts=0) diff --git a/tests/nvme_test.py b/tests/nvme_test.py index d5eca180dd..5162823ac3 100644 --- a/tests/nvme_test.py +++ b/tests/nvme_test.py @@ -58,11 +58,13 @@ def setUp(self): self.ctrl = "XXX" self.ns1 = "XXX" self.test_log_dir = "XXX" + self.do_validate_pci_device = True self.default_nsid = 0x1 self.config_file = 'tests/config.json' self.load_config() - self.validate_pci_device() + if self.do_validate_pci_device: + self.validate_pci_device() def tearDown(self): """ Post Section for TestNVMe. """ @@ -93,6 +95,7 @@ def load_config(self): self.ctrl = config['controller'] self.ns1 = config['ns1'] self.log_dir = config['log_dir'] + self.do_validate_pci_device = config.get('do_validate_pci_device', self.do_validate_pci_device) self.clear_log_dir = False if self.clear_log_dir is True: @@ -228,6 +231,29 @@ def get_ncap(self): print(ncap) return int(ncap) + def get_ocfs(self): + """ Wrapper for extracting optional copy formats supported + - Args: + - None + - Returns: + - Optional Copy Formats Supported + """ + pattern = re.compile("^ocfs[ ]+: 0x[0-9a-fA-F]+", re.IGNORECASE) + ocfs = 0 + max_ns_cmd = "nvme id-ctrl " + self.ctrl + proc = subprocess.Popen(max_ns_cmd, + shell=True, + stdout=subprocess.PIPE, + encoding='utf-8') + err = proc.wait() + self.assertEqual(err, 0, "ERROR : reading optional copy formats supported failed") + + for line in proc.stdout: + if pattern.match(line): + ocfs = line.split(":")[1].strip() + break + return int(ocfs, 16) + def get_format(self): """ Wrapper for extracting format. - Args: