Skip to content

Commit

Permalink
nvme-copy: support cross-namespace copy
Browse files Browse the repository at this point in the history
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
  • Loading branch information
jgu-pure committed Oct 23, 2023
1 parent d812d09 commit 1773bd8
Show file tree
Hide file tree
Showing 6 changed files with 195 additions and 25 deletions.
10 changes: 10 additions & 0 deletions Documentation/nvme-copy.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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>]
Expand Down Expand Up @@ -44,6 +46,14 @@ OPTIONS
-s <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

--limited-retry::
-l::
Sets the limited retry flag.
Expand Down
12 changes: 12 additions & 0 deletions libnvme-wrap.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
25 changes: 21 additions & 4 deletions nvme-print-stdout.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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);
Expand Down
39 changes: 38 additions & 1 deletion nvme.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)";
Expand All @@ -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 };
Expand All @@ -6589,13 +6595,17 @@ 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 {
__u32 namespace_id;
__u64 sdlba;
char *slbas;
char *nlbs;
char *snsids;
char *sopts;
bool lr;
bool fua;
__u8 prinfow;
Expand All @@ -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,
Expand All @@ -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),
Expand All @@ -6656,13 +6670,21 @@ 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,
ARRAY_SIZE(eilbrts.f0));
} 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;
Expand All @@ -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;
Expand All @@ -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),
Expand Down
106 changes: 87 additions & 19 deletions tests/nvme_copy_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,56 +4,124 @@
#
# Copyright (c) 2022 Samsung Electronics Co., Ltd. All Rights Reserved.
#
# Author: Arunpandian J <[email protected]>
# Authors: Arunpandian J <[email protected]>
# Joy Gu <[email protected]>

"""
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)
Loading

0 comments on commit 1773bd8

Please sign in to comment.