1010"""
1111NVMe Copy Testcase:-
1212
13- 1. Issue copy command on set of block; shall pass.
14- 2. If cross-namespace copy formats are supported, enable and test
15- cross-namespace copy formats.
13+ Test classes are split by descriptor format group:
14+
15+ TestNVMeCopyFormat0 - Descriptor Format 0 (16-bit guard, in-namespace copy).
16+ TestNVMeCopyFormat1 - Descriptor Format 1 (64-bit guard, in-namespace copy).
17+ The namespace is reformatted to a 64-bit guard LBA
18+ format before the test runs.
19+ TestNVMeCopyFormat23 - Descriptor Formats 2 and 3 (cross-namespace copy).
20+ Format 3 additionally requires a 64-bit guard namespace
21+ and reformats before running.
1622
1723"""
1824
2430class TestNVMeCopy (TestNVMe ):
2531
2632 """
27- Represents NVMe Copy testcase.
33+ Base class for NVMe Copy tests.
34+
35+ Provides shared setUp/tearDown and helper methods used by all copy test
36+ subclasses.
2837 - Attributes:
29- - ocfs : optional copy formats supported
30- - original_cdfe : saved cdfe value to restore during teardown, or None
31- - test_log_dir : directory for logs, temp files.
38+ - ocfs : optional copy formats supported (from id-ctrl)
39+ - original_cdfe : saved cdfe value restored in tearDown, or None
40+ - mcl : Maximum Copy Length (blocks)
41+ - mssrl : Maximum Single Source Range Length (blocks)
42+ - msrc : Maximum Source Range Count
43+ - ns1_nsid : numeric namespace ID of self.ns1
3244 """
3345
3446 def setUp (self ):
3547 """ Pre Section for TestNVMeCopy """
3648 super ().setUp ()
3749 self .ocfs = self .get_ocfs ()
3850 self .original_cdfe = None
39- self .mcl = to_decimal (self .get_id_ns_field_value ("mcl" ))
40- self .mssrl = to_decimal (self .get_id_ns_field_value ("mssrl" ))
41- self .msrc = to_decimal (self .get_id_ns_field_value ("msrc" ))
51+ self ._refresh_ns_copy_limits ()
4252 get_ns_id_cmd = f"{ self .nvme_bin } get-ns-id { self .ns1 } "
4353 result = self .run_cmd (get_ns_id_cmd )
44- err = result .returncode
45- self .assertEqual (err , 0 , "ERROR : nvme get-ns-id failed" )
46- output = result .stdout
47- self .ns1_nsid = int (output .strip ().split (':' )[- 1 ])
54+ self .assertEqual (result .returncode , 0 , "ERROR : nvme get-ns-id failed" )
55+ self .ns1_nsid = int (result .stdout .strip ().split (':' )[- 1 ])
4856 self .setup_log_dir (self .__class__ .__name__ )
4957
5058 def tearDown (self ):
@@ -55,6 +63,16 @@ def tearDown(self):
5563 self .run_cmd (set_features_cmd )
5664 super ().tearDown ()
5765
66+ # ------------------------------------------------------------------
67+ # Internal helpers
68+ # ------------------------------------------------------------------
69+
70+ def _refresh_ns_copy_limits (self ):
71+ """ Read MCL, MSSRL, MSRC from the current namespace into instance attrs """
72+ self .mcl = to_decimal (self .get_id_ns_field_value ("mcl" ))
73+ self .mssrl = to_decimal (self .get_id_ns_field_value ("mssrl" ))
74+ self .msrc = to_decimal (self .get_id_ns_field_value ("msrc" ))
75+
5876 def _check_format_supported (self , desc_format ):
5977 """ Skip test if the given copy descriptor format is not supported """
6078 if not self .ocfs & (1 << desc_format ):
@@ -68,10 +86,81 @@ def _check_ns_copy_limits(self):
6886 if missing :
6987 self .skipTest (f"{ ', ' .join (missing )} are 0, copy not supported on this namespace" )
7088
89+ def _find_64b_guard_lbaf_index (self ):
90+ """
91+ Search the nvm-id-ns elbafs for a format with 64-bit guard PI (pif == 2).
92+
93+ Returns the lbaf index (0-based position in the lbafs[] array), or None
94+ if no such format exists or the nvm-id-ns command is not supported.
95+ """
96+ nvm_id_ns_cmd = f"{ self .nvme_bin } nvm-id-ns { self .ns1 } --output-format=json"
97+ result = self .run_cmd (nvm_id_ns_cmd )
98+ if result .returncode != 0 :
99+ return None
100+ elbafs = json .loads (result .stdout ).get ("elbafs" , [])
101+ for i , elbaf in enumerate (elbafs ):
102+ if elbaf .get ("pif" , 0 ) == 2 : # NVME_NVM_PIF_64B_GUARD = 2
103+ return i
104+ return None
105+
106+ def _create_ns_with_lbaf (self , lbaf_index ):
107+ """
108+ Delete and recreate the default namespace using the given lbaf_index.
109+
110+ The lbaf_index is encoded into the flbas byte per NVMe spec:
111+ flbas[3:0] = lbaf_index[3:0], flbas[6:5] = lbaf_index[5:4]
112+
113+ After recreating, self.mcl/mssrl/msrc are refreshed from the new
114+ namespace. Calls skipTest if namespace management is not supported
115+ or if the create/attach step fails.
116+ """
117+ if not self .ns_mgmt_supported :
118+ self .skipTest ("namespace management not supported; cannot reformat namespace" )
119+
120+ # encode lbaf_index into the 8-bit flbas field
121+ flbas = (lbaf_index & 0xF ) | (((lbaf_index >> 4 ) & 0x3 ) << 5 )
122+
123+ # get_lba_format_size() in the parent class indexes the id-ns lbafs[] array
124+ # using self.flbas, so set it to lbaf_index here for the size look-up.
125+ # This is intentional: lbaf_index is the direct array position, while flbas
126+ # (computed above) is the encoded byte passed to create-ns --flbas.
127+ self .flbas = lbaf_index
128+ (ds , ms ) = self .get_lba_format_size ()
129+ if ds == 0 :
130+ self .skipTest (f"lbaf { lbaf_index } reports zero data size; cannot create namespace" )
131+ ncap = int (self .get_ncap () / (ds + ms ))
132+
133+ ctrl_id = self .get_ctrl_id ()
134+ self .delete_all_ns ()
135+ err = self .create_and_validate_ns (self .default_nsid , ncap , ncap , flbas , 0 )
136+ self .assertEqual (err , 0 ,
137+ f"ERROR: failed to create namespace with lbaf { lbaf_index } (flbas={ flbas :#x} )" )
138+ self .assertEqual (self .attach_ns (ctrl_id , self .default_nsid ), 0 ,
139+ "ERROR: failed to attach reformatted namespace" )
140+
141+ # refresh copy limits for the new namespace
142+ self ._refresh_ns_copy_limits ()
143+
144+ def _setup_64b_guard_ns (self ):
145+ """
146+ Reformat the default namespace to a 64-bit guard PI LBA format (pif == 2).
147+
148+ Skips the test if:
149+ - namespace management is not supported, or
150+ - no LBA format with 64-bit guard PI exists on this controller.
151+ """
152+ lbaf_index = self ._find_64b_guard_lbaf_index ()
153+ if lbaf_index is None :
154+ self .skipTest ("no LBA format with 64-bit guard PI (pif=2) found; "
155+ "cannot run copy descriptor format 1/3 test" )
156+ self ._create_ns_with_lbaf (lbaf_index )
157+
71158 def _enable_cdfe_for_format (self , desc_format ):
72- """ Enable the host-behavior-support cdfe bit for the given cross-namespace format.
73- Only the bit corresponding to desc_format is enabled; other bits are left unchanged.
74- The original value is saved in self.original_cdfe for tearDown to restore.
159+ """
160+ Enable the host-behavior-support cdfe bit for the given cross-namespace
161+ copy descriptor format. Only the single required bit is enabled; other
162+ bits are left unchanged. The original value is saved in self.original_cdfe
163+ for tearDown to restore.
75164 """
76165 cdfe_bit = 1 << desc_format
77166 get_features_cmd = f"{ self .nvme_bin } feat host-behavior-support " + \
@@ -110,29 +199,79 @@ def copy(self, sdlba, blocks, slbs, **kwargs):
110199 - None
111200 """
112201 desc_format = kwargs .get ("descriptor_format" , 0 )
113- # build copy command
114202 copy_cmd = f"{ self .nvme_bin } copy { self .ns1 } " + \
115203 f"--format={ desc_format } --sdlba={ sdlba } --blocks={ blocks } " + \
116204 f"--slbs={ slbs } "
117205 if "snsids" in kwargs :
118206 copy_cmd += f" --snsids={ kwargs ['snsids' ]} "
119207 if "sopts" in kwargs :
120208 copy_cmd += f" --sopts={ kwargs ['sopts' ]} "
121- # run and assert success
122209 self .assertEqual (self .exec_cmd (copy_cmd ), 0 )
123210
211+
212+ class TestNVMeCopyFormat0 (TestNVMeCopy ):
213+
214+ """
215+ NVMe Copy tests using Descriptor Format 0.
216+
217+ Format 0 uses 16-bit guard PI and copies within a single namespace.
218+ No special namespace formatting is required.
219+ """
220+
124221 def test_copy_format_0 (self ):
125222 """ Test copy with descriptor format 0 """
126223 self ._check_format_supported (0 )
127224 self ._check_ns_copy_limits ()
128225 self .copy (0 , 1 , 2 , descriptor_format = 0 )
129226
227+
228+ class TestNVMeCopyFormat1 (TestNVMeCopy ):
229+
230+ """
231+ NVMe Copy tests using Descriptor Format 1.
232+
233+ Format 1 uses 64-bit guard PI and copies within a single namespace.
234+ setUp reformats the namespace to a 64-bit guard LBA format; the test is
235+ skipped if no such format is available or namespace management is not
236+ supported.
237+ """
238+
239+ def setUp (self ):
240+ """ Pre Section for TestNVMeCopyFormat1 """
241+ super ().setUp ()
242+ self ._setup_64b_guard_ns ()
243+
130244 def test_copy_format_1 (self ):
131245 """ Test copy with descriptor format 1 """
132246 self ._check_format_supported (1 )
133247 self ._check_ns_copy_limits ()
134248 self .copy (0 , 1 , 2 , descriptor_format = 1 )
135249
250+
251+ class TestNVMeCopyFormat23 (TestNVMeCopy ):
252+
253+ """
254+ NVMe Copy tests using Descriptor Formats 2 and 3.
255+
256+ Formats 2 and 3 perform cross-namespace copy operations.
257+ Format 2 uses 16-bit guard PI and works with the default namespace.
258+ Format 3 uses 64-bit guard PI; those tests reformat the namespace inline
259+ (rather than in setUp) so that format 2 tests can still use the standard
260+ namespace.
261+ """
262+
263+ def _run_format_3_copy (self , ** kwargs ):
264+ """
265+ Reformat the namespace to 64-bit guard PI, check copy limits, enable
266+ cdfe for format 3, then execute the copy command.
267+
268+ Additional keyword arguments are forwarded to self.copy() (e.g. sopts).
269+ """
270+ self ._setup_64b_guard_ns ()
271+ self ._check_ns_copy_limits ()
272+ self ._enable_cdfe_for_format (3 )
273+ self .copy (0 , 1 , 2 , descriptor_format = 3 , snsids = self .ns1_nsid , ** kwargs )
274+
136275 def test_copy_format_2 (self ):
137276 """ Test copy with descriptor format 2 """
138277 self ._check_format_supported (2 )
@@ -150,13 +289,9 @@ def test_copy_format_2_sopts(self):
150289 def test_copy_format_3 (self ):
151290 """ Test copy with descriptor format 3 """
152291 self ._check_format_supported (3 )
153- self ._check_ns_copy_limits ()
154- self ._enable_cdfe_for_format (3 )
155- self .copy (0 , 1 , 2 , descriptor_format = 3 , snsids = self .ns1_nsid )
292+ self ._run_format_3_copy ()
156293
157294 def test_copy_format_3_sopts (self ):
158295 """ Test copy with descriptor format 3 and source options """
159296 self ._check_format_supported (3 )
160- self ._check_ns_copy_limits ()
161- self ._enable_cdfe_for_format (3 )
162- self .copy (0 , 1 , 2 , descriptor_format = 3 , snsids = self .ns1_nsid , sopts = 0 )
297+ self ._run_format_3_copy (sopts = 0 )
0 commit comments