'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>]
--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.
__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,
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",
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");
}
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:
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)";
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 {
__u64 sdlba;
char *slbas;
char *nlbs;
+ char *snsids;
+ char *sopts;
bool lr;
bool fua;
__u8 prinfow;
.sdlba = 0,
.slbas = "",
.nlbs = "",
+ .snsids = "",
+ .sopts = "",
.lr = false,
.fua = false,
.prinfow = 0,
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),
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;
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;
}
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),
#
# 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)
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. """
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:
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: