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 ):
@@ -56,6 +64,16 @@ def tearDown(self):
5664 self .run_cmd (set_features_cmd )
5765 super ().tearDown ()
5866
67+ # ------------------------------------------------------------------
68+ # Internal helpers
69+ # ------------------------------------------------------------------
70+
71+ def _refresh_ns_copy_limits (self ):
72+ """ Read MCL, MSSRL, MSRC from the current namespace into instance attrs """
73+ self .mcl = to_decimal (self .get_id_ns_field_value ("mcl" ))
74+ self .mssrl = to_decimal (self .get_id_ns_field_value ("mssrl" ))
75+ self .msrc = to_decimal (self .get_id_ns_field_value ("msrc" ))
76+
5977 def _check_format_supported (self , desc_format ):
6078 """ Skip test if the given copy descriptor format is not supported """
6179 if not self .ocfs & (1 << desc_format ):
@@ -69,10 +87,81 @@ def _check_ns_copy_limits(self):
6987 if missing :
7088 self .skipTest (f"{ ', ' .join (missing )} are 0, copy not supported on this namespace" )
7189
90+ def _find_64b_guard_lbaf_index (self ):
91+ """
92+ Search the nvm-id-ns elbafs for a format with 64-bit guard PI (pif == 2).
93+
94+ Returns the lbaf index (0-based position in the lbafs[] array), or None
95+ if no such format exists or the nvm-id-ns command is not supported.
96+ """
97+ nvm_id_ns_cmd = f"{ self .nvme_bin } nvm-id-ns { self .ns1 } --output-format=json"
98+ result = self .run_cmd (nvm_id_ns_cmd )
99+ if result .returncode != 0 :
100+ return None
101+ elbafs = json .loads (result .stdout ).get ("elbafs" , [])
102+ for i , elbaf in enumerate (elbafs ):
103+ if elbaf .get ("pif" , 0 ) == 2 : # NVME_NVM_PIF_64B_GUARD = 2
104+ return i
105+ return None
106+
107+ def _create_ns_with_lbaf (self , lbaf_index ):
108+ """
109+ Delete and recreate the default namespace using the given lbaf_index.
110+
111+ The lbaf_index is encoded into the flbas byte per NVMe spec:
112+ flbas[3:0] = lbaf_index[3:0], flbas[6:5] = lbaf_index[5:4]
113+
114+ After recreating, self.mcl/mssrl/msrc are refreshed from the new
115+ namespace. Calls skipTest if namespace management is not supported
116+ or if the create/attach step fails.
117+ """
118+ if not self .ns_mgmt_supported :
119+ self .skipTest ("namespace management not supported; cannot reformat namespace" )
120+
121+ # encode lbaf_index into the 8-bit flbas field
122+ flbas = (lbaf_index & 0xF ) | (((lbaf_index >> 4 ) & 0x3 ) << 5 )
123+
124+ # get_lba_format_size() in the parent class indexes the id-ns lbafs[] array
125+ # using self.flbas, so set it to lbaf_index here for the size look-up.
126+ # This is intentional: lbaf_index is the direct array position, while flbas
127+ # (computed above) is the encoded byte passed to create-ns --flbas.
128+ self .flbas = lbaf_index
129+ (ds , ms ) = self .get_lba_format_size ()
130+ if ds == 0 :
131+ self .skipTest (f"lbaf { lbaf_index } reports zero data size; cannot create namespace" )
132+ ncap = int (self .get_ncap () / (ds + ms ))
133+
134+ ctrl_id = self .get_ctrl_id ()
135+ self .delete_all_ns ()
136+ err = self .create_and_validate_ns (self .default_nsid , ncap , ncap , flbas , 0 )
137+ self .assertEqual (err , 0 ,
138+ f"ERROR: failed to create namespace with lbaf { lbaf_index } (flbas={ flbas :#x} )" )
139+ self .assertEqual (self .attach_ns (ctrl_id , self .default_nsid ), 0 ,
140+ "ERROR: failed to attach reformatted namespace" )
141+
142+ # refresh copy limits for the new namespace
143+ self ._refresh_ns_copy_limits ()
144+
145+ def _setup_64b_guard_ns (self ):
146+ """
147+ Reformat the default namespace to a 64-bit guard PI LBA format (pif == 2).
148+
149+ Skips the test if:
150+ - namespace management is not supported, or
151+ - no LBA format with 64-bit guard PI exists on this controller.
152+ """
153+ lbaf_index = self ._find_64b_guard_lbaf_index ()
154+ if lbaf_index is None :
155+ self .skipTest ("no LBA format with 64-bit guard PI (pif=2) found; "
156+ "cannot run copy descriptor format 1/3 test" )
157+ self ._create_ns_with_lbaf (lbaf_index )
158+
72159 def _enable_cdfe_for_format (self , desc_format ):
73- """ Enable the host-behavior-support cdfe bit for the given cross-namespace format.
74- Only the bit corresponding to desc_format is enabled; other bits are left unchanged.
75- The original value is saved in self.original_cdfe for tearDown to restore.
160+ """
161+ Enable the host-behavior-support cdfe bit for the given cross-namespace
162+ copy descriptor format. Only the single required bit is enabled; other
163+ bits are left unchanged. The original value is saved in self.original_cdfe
164+ for tearDown to restore.
76165 """
77166 cdfe_bit = 1 << desc_format
78167 get_features_cmd = f"{ self .nvme_bin } feat host-behavior-support " + \
@@ -114,29 +203,79 @@ def copy(self, sdlba, blocks, slbs, **kwargs):
114203 - None
115204 """
116205 desc_format = kwargs .get ("descriptor_format" , 0 )
117- # build copy command
118206 copy_cmd = f"{ self .nvme_bin } copy { self .ns1 } " + \
119207 f"--format={ desc_format } --sdlba={ sdlba } --blocks={ blocks } " + \
120208 f"--slbs={ slbs } "
121209 if "snsids" in kwargs :
122210 copy_cmd += f" --snsids={ kwargs ['snsids' ]} "
123211 if "sopts" in kwargs :
124212 copy_cmd += f" --sopts={ kwargs ['sopts' ]} "
125- # run and assert success
126213 self .assertEqual (self .exec_cmd (copy_cmd ), 0 )
127214
215+
216+ class TestNVMeCopyFormat0 (TestNVMeCopy ):
217+
218+ """
219+ NVMe Copy tests using Descriptor Format 0.
220+
221+ Format 0 uses 16-bit guard PI and copies within a single namespace.
222+ No special namespace formatting is required.
223+ """
224+
128225 def test_copy_format_0 (self ):
129226 """ Test copy with descriptor format 0 """
130227 self ._check_format_supported (0 )
131228 self ._check_ns_copy_limits ()
132229 self .copy (0 , 1 , 2 , descriptor_format = 0 )
133230
231+
232+ class TestNVMeCopyFormat1 (TestNVMeCopy ):
233+
234+ """
235+ NVMe Copy tests using Descriptor Format 1.
236+
237+ Format 1 uses 64-bit guard PI and copies within a single namespace.
238+ setUp reformats the namespace to a 64-bit guard LBA format; the test is
239+ skipped if no such format is available or namespace management is not
240+ supported.
241+ """
242+
243+ def setUp (self ):
244+ """ Pre Section for TestNVMeCopyFormat1 """
245+ super ().setUp ()
246+ self ._setup_64b_guard_ns ()
247+
134248 def test_copy_format_1 (self ):
135249 """ Test copy with descriptor format 1 """
136250 self ._check_format_supported (1 )
137251 self ._check_ns_copy_limits ()
138252 self .copy (0 , 1 , 2 , descriptor_format = 1 )
139253
254+
255+ class TestNVMeCopyFormat23 (TestNVMeCopy ):
256+
257+ """
258+ NVMe Copy tests using Descriptor Formats 2 and 3.
259+
260+ Formats 2 and 3 perform cross-namespace copy operations.
261+ Format 2 uses 16-bit guard PI and works with the default namespace.
262+ Format 3 uses 64-bit guard PI; those tests reformat the namespace inline
263+ (rather than in setUp) so that format 2 tests can still use the standard
264+ namespace.
265+ """
266+
267+ def _run_format_3_copy (self , ** kwargs ):
268+ """
269+ Reformat the namespace to 64-bit guard PI, check copy limits, enable
270+ cdfe for format 3, then execute the copy command.
271+
272+ Additional keyword arguments are forwarded to self.copy() (e.g. sopts).
273+ """
274+ self ._setup_64b_guard_ns ()
275+ self ._check_ns_copy_limits ()
276+ self ._enable_cdfe_for_format (3 )
277+ self .copy (0 , 1 , 2 , descriptor_format = 3 , snsids = self .ns1_nsid , ** kwargs )
278+
140279 def test_copy_format_2 (self ):
141280 """ Test copy with descriptor format 2 """
142281 self ._check_format_supported (2 )
@@ -154,13 +293,9 @@ def test_copy_format_2_sopts(self):
154293 def test_copy_format_3 (self ):
155294 """ Test copy with descriptor format 3 """
156295 self ._check_format_supported (3 )
157- self ._check_ns_copy_limits ()
158- self ._enable_cdfe_for_format (3 )
159- self .copy (0 , 1 , 2 , descriptor_format = 3 , snsids = self .ns1_nsid )
296+ self ._run_format_3_copy ()
160297
161298 def test_copy_format_3_sopts (self ):
162299 """ Test copy with descriptor format 3 and source options """
163300 self ._check_format_supported (3 )
164- self ._check_ns_copy_limits ()
165- self ._enable_cdfe_for_format (3 )
166- self .copy (0 , 1 , 2 , descriptor_format = 3 , snsids = self .ns1_nsid , sopts = 0 )
301+ self ._run_format_3_copy (sopts = 0 )
0 commit comments