]> www.infradead.org Git - users/sagi/nvme-cli.git/commitdiff
nvme-copy: support cross-namespace copy
authorJoy Gu <jgu@purestorage.com>
Wed, 25 Oct 2023 20:38:25 +0000 (13:38 -0700)
committerDaniel Wagner <wagi@monom.org>
Tue, 7 Nov 2023 08:00:08 +0000 (09:00 +0100)
Add support for NVMe TP4130 ("Cross-Namespace Copy")
- Add Copy Descriptor Formats 2h and 3h
- Add "--snsids" option to specify source namespaces to copy from
- Add "--sopts" option to specify source options (Fast Copy Only)
- Print new Host Behavior Support bits, new ONCS bits, and new Optional
  Copy Formats Supported bits
- Extend unit test case to test cross-namespace copy formats

Documentation/nvme-copy.txt
libnvme-wrap.c
nvme-print-stdout.c
nvme.c
tests/nvme_copy_test.py
tests/nvme_test.py

index e8dcc6c7dfa364313bbf3883f9fa5b5020182360..7c5fb0e65d0899c1698e293dc4fad829a1543dcc 100644 (file)
@@ -11,6 +11,8 @@ SYNOPSIS
 'nvme-copy' <device> [--sdlba=<sdlba> | -d <sdlba>]
                        [--blocks=<nlb-list,> | -b <nlb-list,>]
                        [--slbs=<slbas,> | -s <slbas,>]
+                       [--snsids=<snsids,> | -N <snsids,>]
+                       [--sopts=<sopts,> | -O <sopts,>]
                        [--limited-retry | -l]
                        [--force-unit-access | -f]
                        [--prinfow=<prinfow> | -p <prinfow>]
@@ -45,6 +47,14 @@ OPTIONS
 --slbs=<slbas,>::
        Comma separated list of the starting blocks in each range
 
+--snsids=<snsids,>::
+-N <snsids,>::
+       Comma separated list of the source namespace identifiers in each range
+
+--sopts=<sopts,>::
+-O <sopts,>::
+       Comma separated list of the source options in each range
+
 -l::
 --limited-retry::
        Sets the limited retry flag.
index 354e7f7afa3a43fac723c7a25bb6128036697d7c..a62d60c0123e33a62006eb053b182a1762d97b01 100644 (file)
@@ -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,
index ba7d12e173a7cca4e3fa10368765672dfdafc60c..ada33a17233707125f2e52db0089954085263988 100644 (file)
@@ -2167,19 +2167,25 @@ 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 copy = (oncs & 0x100) >> 8;
-       __u16 vrfy = (oncs & 0x80) >> 7;
-       __u16 tmst = (oncs & 0x40) >> 6;
-       __u16 resv = (oncs & 0x20) >> 5;
-       __u16 save = (oncs & 0x10) >> 4;
-       __u16 wzro = (oncs & 0x8) >> 3;
-       __u16 dsms = (oncs & 0x4) >> 2;
-       __u16 wunc = (oncs & 0x2) >> 1;
-       __u16 cmp = oncs & 0x1;
+       __u16 rsvd = oncs >> 11;
+       bool afc  = !!(oncs & NVME_CTRL_ONCS_ALL_FAST_COPY);
+       bool csa  = !!(oncs & NVME_CTRL_ONCS_COPY_SINGLE_ATOMICITY);
+       bool copy = !!(oncs & NVME_CTRL_ONCS_COPY);
+       bool vrfy = !!(oncs & NVME_CTRL_ONCS_VERIFY);
+       bool tmst = !!(oncs & NVME_CTRL_ONCS_TIMESTAMP);
+       bool resv = !!(oncs & NVME_CTRL_ONCS_RESERVATIONS);
+       bool save = !!(oncs & NVME_CTRL_ONCS_SAVE_FEATURES);
+       bool wzro = !!(oncs & NVME_CTRL_ONCS_WRITE_ZEROES);
+       bool dsms = !!(oncs & NVME_CTRL_ONCS_DSM);
+       bool wunc = !!(oncs & NVME_CTRL_ONCS_WRITE_UNCORRECTABLE);
+       bool cmp  = !!(oncs & NVME_CTRL_ONCS_COMPARE);
 
        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",
@@ -2288,15 +2294,16 @@ static void stdout_id_ctrl_nwpc(__u8 nwpc)
 static void stdout_id_ctrl_ocfs(__le16 ctrl_ocfs)
 {
        __u16 ocfs = le16_to_cpu(ctrl_ocfs);
-       __u16 rsvd = (ocfs & 0xfffc) >> 2;
-       __u8 copy_fmt_1 = (ocfs >> 1) & 0x1;
-       __u8 copy_fmt_0 = ocfs & 0x1;
+       __u16 rsvd = ocfs >> 4;
+       __u8 copy_fmt_supported;
+       int copy_fmt;
        if (rsvd)
-               printf("  [15:2] : %#x\tReserved\n", rsvd);
-       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",
-               copy_fmt_0, copy_fmt_0 ? "" : "Not ");
+               printf("  [15:4] : %#x\tReserved\n", rsvd);
+       for (copy_fmt = 3; copy_fmt >= 0; copy_fmt--) {
+               copy_fmt_supported = ocfs >> copy_fmt & 1;
+               printf("  [%d:%d] : %#x\tController Copy Format %xh %sSupported\n", copy_fmt, copy_fmt,
+                       copy_fmt_supported, copy_fmt, copy_fmt_supported ? "" : "Not ");
+       }
        printf("\n");
 }
 
@@ -4483,6 +4490,10 @@ static void stdout_feature_show_fields(enum nvme_features_id fid,
                               host_behavior->etdas ? "True" : "False");
                        printf("\tLBA Format Extension Enable (LBAFEE): %s\n",
                               host_behavior->lbafee ? "True" : "False");
+                       printf("\tCopy Descriptor Format 2h Enabled (CDFE): %s\n",
+                              host_behavior->cdfe & (1 << 2) ? "True" : "False");
+                       printf("\tCopy Descriptor Format 3h Enabled (CDFE): %s\n",
+                              host_behavior->cdfe & (1 << 3) ? "True" : "False");
                }
                break;
        case NVME_FEAT_FID_SANITIZE:
diff --git a/nvme.c b/nvme.c
index 1c4cf42443ca101837bbe2c0d340031985867da9..24f5329041cedefe4bb780be67eda6499cc05ffb 100644 (file)
--- a/nvme.c
+++ b/nvme.c
@@ -6554,6 +6554,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)";
@@ -6569,22 +6571,26 @@ 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;
-       __u16 nlbs[128] = { 0 };
-       __u64 slbas[128] = { 0 };
+       __u16 nr, nb, ns, nrts, natms, nats, nids;
+       __u16 nlbs[256] = { 0 };
+       __u64 slbas[256] = { 0 };
+       __u32 snsids[256] = { 0 };
+       __u16 sopts[256] = { 0 };
        int err;
 
        union {
-               __u32 f0[128];
-               __u64 f1[101];
+               __u32 short_pi[256];
+               __u64 long_pi[256];
        } eilbrts;
 
-       __u32 elbatms[128] = { 0 };
-       __u32 elbats[128] = { 0 };
+       __u32 elbatms[256] = { 0 };
+       __u32 elbats[256] = { 0 };
 
        union {
-               struct nvme_copy_range f0[128];
-               struct nvme_copy_range_f1 f1[101];
+               struct nvme_copy_range f0[256];
+               struct nvme_copy_range_f1 f1[256];
+               struct nvme_copy_range_f2 f2[256];
+               struct nvme_copy_range_f3 f3[256];
        } *copy;
 
        struct config {
@@ -6592,6 +6598,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;
@@ -6612,6 +6620,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,
@@ -6632,6 +6642,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),
@@ -6652,13 +6664,15 @@ 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));
-
-       if (cfg.format == 0) {
-               nrts = argconfig_parse_comma_sep_array_u32(cfg.eilbrts, eilbrts.f0,
-                                                          ARRAY_SIZE(eilbrts.f0));
-       } else if (cfg.format == 1) {
-               nrts = argconfig_parse_comma_sep_array_u64(cfg.eilbrts, eilbrts.f1,
-                                                          ARRAY_SIZE(eilbrts.f1));
+       nids = 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 || cfg.format == 2) {
+               nrts = argconfig_parse_comma_sep_array_u32(cfg.eilbrts, eilbrts.short_pi,
+                                                          ARRAY_SIZE(eilbrts.short_pi));
+       } else if (cfg.format == 1 || cfg.format == 3) {
+               nrts = argconfig_parse_comma_sep_array_u64(cfg.eilbrts, eilbrts.long_pi,
+                                                          ARRAY_SIZE(eilbrts.long_pi));
        } else {
                nvme_show_error("invalid format");
                return -EINVAL;
@@ -6668,7 +6682,16 @@ 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 (!nr || nr > 128 || (cfg.format == 1 && nr > 101)) {
+       if (cfg.format == 2 || cfg.format == 3) {
+               if (nr != nids) {
+                       nvme_show_error("formats 2 and 3 require source namespace ids for each source range");
+                       return -EINVAL;
+               }
+       } else if (nids) {
+               nvme_show_error("formats 0 and 1 do not support cross-namespace copy");
+               return -EINVAL;
+       }
+       if (!nr || nr > 256) {
                nvme_show_error("invalid range");
                return -EINVAL;
        }
@@ -6686,9 +6709,15 @@ static int copy_cmd(int argc, char **argv, struct command *cmd, struct plugin *p
                return -ENOMEM;
 
        if (cfg.format == 0)
-               nvme_init_copy_range(copy->f0, nlbs, slbas, eilbrts.f0, elbatms, elbats, nr);
+               nvme_init_copy_range(copy->f0, nlbs, slbas, eilbrts.short_pi, elbatms, elbats, nr);
        else if (cfg.format == 1)
-               nvme_init_copy_range_f1(copy->f1, nlbs, slbas, eilbrts.f1, elbatms, elbats, nr);
+               nvme_init_copy_range_f1(copy->f1, nlbs, slbas, eilbrts.long_pi, elbatms, elbats, nr);
+       else if (cfg.format == 2)
+               nvme_init_copy_range_f2(copy->f2, snsids, nlbs, slbas, sopts, eilbrts.short_pi, elbatms,
+                                       elbats, nr);
+       else if (cfg.format == 3)
+               nvme_init_copy_range_f3(copy->f3, snsids, nlbs, slbas, sopts, eilbrts.long_pi, elbatms,
+                                       elbats, nr);
 
        struct nvme_copy_args args = {
                .args_size      = sizeof(args),
index d84a6d243a70af81f449c73a82ec00405cefe792..a5472312fa2fac3829e2a1730492e777e036bda7 100644 (file)
 #
 # Copyright (c) 2022 Samsung Electronics Co., Ltd. All Rights Reserved.
 #
-# Author: Arunpandian J <apj.arun@samsung.com>
+# Authors: Arunpandian J <apj.arun@samsung.com>
+#          Joy Gu <jgu@purestorage.com>
 
 """
 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", "--data-len=512", "-b"]
+            print("Running command:", " ".join(get_features_cmd))
+            self.host_behavior_data = subprocess.check_output(get_features_cmd)
+            # enable cross-namespace copy formats
+            if self.host_behavior_data[4] & cross_namespace_copy:
+                # skip if already enabled
+                print("Cross-namespace copy already enabled, skipping set-features")
+                self.host_behavior_data = None
+            else:
+                data = self.host_behavior_data[:4] + cross_namespace_copy.to_bytes(2, 'little') + self.host_behavior_data[6:]
+                set_features_cmd = ["nvme", "set-feature", self.ctrl, "--feature-id=0x16", "--data-len=512"]
+                print("Running command:", " ".join(set_features_cmd))
+                proc = subprocess.Popen(set_features_cmd,
+                                        stdout=subprocess.PIPE,
+                                        stdin=subprocess.PIPE)
+                proc.communicate(input=data)
+                self.assertEqual(proc.returncode, 0, "Failed to enable cross-namespace copy formats")
+        get_ns_id_cmd = ["nvme", "get-ns-id", self.ns1]
+        print("Running command:", " ".join(get_ns_id_cmd))
+        output = subprocess.check_output(get_ns_id_cmd)
+        self.ns1_nsid = int(output.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", "--data-len=512"]
+            print("Running command:", " ".join(set_features_cmd))
+            proc = subprocess.Popen(set_features_cmd,
+                                    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 = f"nvme copy {self.ns1} --format={desc_format} --sdlba={sdlba} --blocks={blocks} --slbs={slbs}"
+        if "snsids" in kwargs:
+            copy_cmd += f" --snsids={kwargs['snsids']}"
+        if "sopts" in kwargs:
+            copy_cmd += f" --sopts={kwargs['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)
index e76193da787d4ea10a070743e78076e1b8f6c347..0df3dacc2c6bb39931d30092e1e0baec55ea1a5e 100644 (file)
@@ -58,11 +58,13 @@ class TestNVMe(unittest.TestCase):
         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 @@ class TestNVMe(unittest.TestCase):
             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,19 @@ class TestNVMe(unittest.TestCase):
         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(r'^ocfs\s*: 0x[0-9a-fA-F]+$')
+        output = subprocess.check_output(["nvme", "id-ctrl", self.ctrl], encoding='utf-8')
+        ocfs_line = next(line for line in output.splitlines() if pattern.match(line))
+        ocfs = ocfs_line.split(":")[1].strip()
+        return int(ocfs, 16)
+
     def get_format(self):
         """ Wrapper for extracting format.
             - Args: