Merge branch 'master' of https://github.com/celestinechen/fio
[fio.git] / t / nvmept_pi.py
1 #!/usr/bin/env python3
2 """
3 # nvmept_pi.py
4 #
5 # Test fio's io_uring_cmd ioengine support for DIF/DIX end-to-end data
6 # protection.
7 #
8 # USAGE
9 # see python3 nvmept_pi.py --help
10 #
11 # EXAMPLES (THIS IS A DESTRUCTIVE TEST!!)
12 # python3 t/nvmept_pi.py --dut /dev/ng0n1 -f ./fio
13 # python3 t/nvmept_pi.py --dut /dev/ng0n1 -f ./fio --lbaf 1
14 #
15 # REQUIREMENTS
16 # Python 3.6
17 #
18 """
19 import os
20 import sys
21 import json
22 import time
23 import locale
24 import logging
25 import argparse
26 import itertools
27 import subprocess
28 from pathlib import Path
29 from fiotestlib import FioJobCmdTest, run_fio_tests
30 from fiotestcommon import SUCCESS_NONZERO
31
32 NUMBER_IOS = 8192
33 BS_LOW = 1
34 BS_HIGH = 16
35
36 class DifDixTest(FioJobCmdTest):
37     """
38     NVMe DIF/DIX test class.
39     """
40
41     def setup(self, parameters):
42         """Setup a test."""
43
44         fio_args = [
45             "--name=nvmept_pi",
46             f"--ioengine={self.fio_opts['ioengine']}",
47             f"--filename={self.fio_opts['filename']}",
48             f"--rw={self.fio_opts['rw']}",
49             f"--bsrange={self.fio_opts['bsrange']}",
50             f"--output={self.filenames['output']}",
51             f"--md_per_io_size={self.fio_opts['md_per_io_size']}",
52             f"--pi_act={self.fio_opts['pi_act']}",
53             f"--pi_chk={self.fio_opts['pi_chk']}",
54             f"--apptag={self.fio_opts['apptag']}",
55             f"--apptag_mask={self.fio_opts['apptag_mask']}",
56         ]
57         for opt in ['fixedbufs', 'nonvectored', 'force_async', 'registerfiles',
58                     'sqthread_poll', 'sqthread_poll_cpu', 'hipri', 'nowait',
59                     'time_based', 'runtime', 'verify', 'io_size', 'offset', 'number_ios',
60                     'output-format']:
61             if opt in self.fio_opts:
62                 option = f"--{opt}={self.fio_opts[opt]}"
63                 fio_args.append(option)
64
65         if self.fio_opts['ioengine'] == 'io_uring_cmd':
66             fio_args.append('--cmd_type=nvme')
67         elif self.fio_opts['ioengine'] == 'xnvme':
68             fio_args.append('--thread=1')
69             fio_args.append('--xnvme_async=io_uring_cmd')
70
71         super().setup(fio_args)
72
73
74 TEST_LIST = [
75 #
76 # Write data with pi_act=1 and then read the data back (with both
77 # pi_act=[0,1]).
78 #
79     {
80         # Write workload with variable IO sizes
81         # pi_act=1
82         "test_id": 101,
83         "fio_opts": {
84             "rw": 'write',
85             "number_ios": NUMBER_IOS,
86             "output-format": "json",
87             "apptag": "0x8888",
88             "apptag_mask": "0xFFFF",
89             "pi_act": 1,
90             },
91         "pi_chk": "APPTAG,GUARD,REFTAG",
92         "bs_low": BS_LOW,
93         "bs_high": BS_HIGH,
94         "test_class": DifDixTest,
95     },
96     {
97         # Read workload with fixed small IO size
98         # pi_act=0
99         "test_id": 102,
100         "fio_opts": {
101             "rw": 'read',
102             "number_ios": NUMBER_IOS,
103             "output-format": "json",
104             "pi_act": 0,
105             "apptag": "0x8888",
106             "apptag_mask": "0xFFFF",
107             },
108         "pi_chk": "APPTAG,GUARD,REFTAG",
109         "bs_low": BS_LOW,
110         "bs_high": BS_LOW,
111         "test_class": DifDixTest,
112     },
113     {
114         # Read workload with fixed small IO size
115         # pi_act=1
116         "test_id": 103,
117         "fio_opts": {
118             "rw": 'read',
119             "number_ios": NUMBER_IOS,
120             "output-format": "json",
121             "pi_act": 1,
122             "apptag": "0x8888",
123             "apptag_mask": "0xFFFF",
124             },
125         "pi_chk": "APPTAG,GUARD,REFTAG",
126         "bs_low": BS_LOW,
127         "bs_high": BS_LOW,
128         "test_class": DifDixTest,
129     },
130     {
131         # Write workload with fixed large IO size
132         # Precondition for read workloads to follow
133         # pi_act=1
134         "test_id": 104,
135         "fio_opts": {
136             "rw": 'write',
137             "number_ios": NUMBER_IOS,
138             "output-format": "json",
139             "apptag": "0x8888",
140             "apptag_mask": "0xFFFF",
141             "pi_act": 1,
142             },
143         "pi_chk": "APPTAG,GUARD,REFTAG",
144         "bs_low": BS_HIGH,
145         "bs_high": BS_HIGH,
146         "test_class": DifDixTest,
147     },
148     {
149         # Read workload with variable IO sizes
150         # pi_act=0
151         "test_id": 105,
152         "fio_opts": {
153             "rw": 'read',
154             "number_ios": NUMBER_IOS,
155             "output-format": "json",
156             "pi_act": 0,
157             "apptag": "0x8888",
158             "apptag_mask": "0xFFFF",
159             },
160         "pi_chk": "APPTAG,GUARD,REFTAG",
161         "bs_low": BS_LOW,
162         "bs_high": BS_HIGH,
163         "test_class": DifDixTest,
164     },
165     {
166         # Read workload with variable IO sizes
167         # pi_act=1
168         "test_id": 106,
169         "fio_opts": {
170             "rw": 'read',
171             "number_ios": NUMBER_IOS,
172             "output-format": "json",
173             "pi_act": 1,
174             "apptag": "0x8888",
175             "apptag_mask": "0xFFFF",
176             },
177         "pi_chk": "APPTAG,GUARD,REFTAG",
178         "bs_low": BS_LOW,
179         "bs_high": BS_HIGH,
180         "test_class": DifDixTest,
181     },
182 #
183 # Write data with pi_act=0 and then read the data back (with both
184 # pi_act=[0,1]).
185 #
186     {
187         # Write workload with variable IO sizes
188         # pi_act=0
189         "test_id": 201,
190         "fio_opts": {
191             "rw": 'write',
192             "number_ios": NUMBER_IOS,
193             "output-format": "json",
194             "apptag": "0x8888",
195             "apptag_mask": "0xFFFF",
196             "pi_act": 0,
197             },
198         "pi_chk": "APPTAG,GUARD,REFTAG",
199         "bs_low": BS_LOW,
200         "bs_high": BS_HIGH,
201         "test_class": DifDixTest,
202     },
203     {
204         # Read workload with fixed small IO size
205         # pi_act=0
206         "test_id": 202,
207         "fio_opts": {
208             "rw": 'read',
209             "number_ios": NUMBER_IOS,
210             "output-format": "json",
211             "pi_act": 0,
212             "apptag": "0x8888",
213             "apptag_mask": "0xFFFF",
214             },
215         "pi_chk": "APPTAG,GUARD,REFTAG",
216         "bs_low": BS_LOW,
217         "bs_high": BS_LOW,
218         "test_class": DifDixTest,
219     },
220     {
221         # Read workload with fixed small IO size
222         # pi_act=1
223         "test_id": 203,
224         "fio_opts": {
225             "rw": 'read',
226             "number_ios": NUMBER_IOS,
227             "output-format": "json",
228             "pi_act": 1,
229             "apptag": "0x8888",
230             "apptag_mask": "0xFFFF",
231             },
232         "pi_chk": "APPTAG,GUARD,REFTAG",
233         "bs_low": BS_LOW,
234         "bs_high": BS_LOW,
235         "test_class": DifDixTest,
236     },
237     {
238         # Write workload with fixed large IO sizes
239         # pi_act=0
240         "test_id": 204,
241         "fio_opts": {
242             "rw": 'write',
243             "number_ios": NUMBER_IOS,
244             "output-format": "json",
245             "apptag": "0x8888",
246             "apptag_mask": "0xFFFF",
247             "pi_act": 0,
248             },
249         "pi_chk": "APPTAG,GUARD,REFTAG",
250         "bs_low": BS_HIGH,
251         "bs_high": BS_HIGH,
252         "test_class": DifDixTest,
253     },
254     {
255         # Read workload with variable IO sizes
256         # pi_act=0
257         "test_id": 205,
258         "fio_opts": {
259             "rw": 'read',
260             "number_ios": NUMBER_IOS,
261             "output-format": "json",
262             "pi_act": 0,
263             "apptag": "0x8888",
264             "apptag_mask": "0xFFFF",
265             },
266         "pi_chk": "APPTAG,GUARD,REFTAG",
267         "bs_low": BS_LOW,
268         "bs_high": BS_HIGH,
269         "test_class": DifDixTest,
270     },
271     {
272         # Read workload with variable IO sizes
273         # pi_act=1
274         "test_id": 206,
275         "fio_opts": {
276             "rw": 'read',
277             "number_ios": NUMBER_IOS,
278             "output-format": "json",
279             "pi_act": 1,
280             "apptag": "0x8888",
281             "apptag_mask": "0xFFFF",
282             },
283         "pi_chk": "APPTAG,GUARD,REFTAG",
284         "bs_low": BS_LOW,
285         "bs_high": BS_HIGH,
286         "test_class": DifDixTest,
287     },
288 #
289 # Test apptag errors.
290 #
291     {
292         # Read workload with variable IO sizes
293         # pi_act=0
294         # trigger an apptag error
295         "test_id": 301,
296         "fio_opts": {
297             "rw": 'read',
298             "number_ios": NUMBER_IOS,
299             "output-format": "json",
300             "pi_act": 0,
301             "apptag": "0x0888",
302             "apptag_mask": "0xFFFF",
303             },
304         "pi_chk": "APPTAG,GUARD,REFTAG",
305         "bs_low": BS_LOW,
306         "bs_high": BS_HIGH,
307         "success": SUCCESS_NONZERO,
308         "test_class": DifDixTest,
309     },
310     {
311         # Read workload with variable IO sizes
312         # pi_act=1
313         # trigger an apptag error
314         "test_id": 302,
315         "fio_opts": {
316             "rw": 'read',
317             "number_ios": NUMBER_IOS,
318             "output-format": "json",
319             "pi_act": 1,
320             "apptag": "0x0888",
321             "apptag_mask": "0xFFFF",
322             },
323         "pi_chk": "APPTAG,GUARD,REFTAG",
324         "bs_low": BS_LOW,
325         "bs_high": BS_HIGH,
326         "success": SUCCESS_NONZERO,
327         "test_class": DifDixTest,
328     },
329     {
330         # Read workload with variable IO sizes
331         # pi_act=0
332         # trigger an apptag error
333         # same as above but with pi_chk=APPTAG only
334         "test_id": 303,
335         "fio_opts": {
336             "rw": 'read',
337             "number_ios": NUMBER_IOS,
338             "output-format": "json",
339             "pi_act": 0,
340             "apptag": "0x0888",
341             "apptag_mask": "0xFFFF",
342             },
343         "pi_chk": "APPTAG",
344         "bs_low": BS_LOW,
345         "bs_high": BS_HIGH,
346         "success": SUCCESS_NONZERO,
347         "test_class": DifDixTest,
348     },
349     {
350         # Read workload with variable IO sizes
351         # pi_act=1
352         # trigger an apptag error
353         # same as above but with pi_chk=APPTAG only
354         "test_id": 304,
355         "fio_opts": {
356             "rw": 'read',
357             "number_ios": NUMBER_IOS,
358             "output-format": "json",
359             "pi_act": 1,
360             "apptag": "0x0888",
361             "apptag_mask": "0xFFFF",
362             },
363         "pi_chk": "APPTAG",
364         "bs_low": BS_LOW,
365         "bs_high": BS_HIGH,
366         "success": SUCCESS_NONZERO,
367         "test_class": DifDixTest,
368     },
369     {
370         # Read workload with variable IO sizes
371         # pi_act=0
372         # this case would trigger an apptag error, but pi_chk says to check
373         # only the Guard PI and reftag, so there should be no error
374         "test_id": 305,
375         "fio_opts": {
376             "rw": 'read',
377             "number_ios": NUMBER_IOS,
378             "output-format": "json",
379             "pi_act": 0,
380             "apptag": "0x0888",
381             "apptag_mask": "0xFFFF",
382             },
383         "pi_chk": "GUARD,REFTAG",
384         "bs_low": BS_LOW,
385         "bs_high": BS_HIGH,
386         "test_class": DifDixTest,
387     },
388     {
389         # Read workload with variable IO sizes
390         # pi_act=1
391         # this case would trigger an apptag error, but pi_chk says to check
392         # only the Guard PI and reftag, so there should be no error
393         "test_id": 306,
394         "fio_opts": {
395             "rw": 'read',
396             "number_ios": NUMBER_IOS,
397             "output-format": "json",
398             "pi_act": 1,
399             "apptag": "0x0888",
400             "apptag_mask": "0xFFFF",
401             },
402         "pi_chk": "GUARD,REFTAG",
403         "bs_low": BS_LOW,
404         "bs_high": BS_HIGH,
405         "test_class": DifDixTest,
406     },
407     {
408         # Read workload with variable IO sizes
409         # pi_act=0
410         # this case would trigger an apptag error, but pi_chk says to check
411         # only the Guard PI, so there should be no error
412         "test_id": 307,
413         "fio_opts": {
414             "rw": 'read',
415             "number_ios": NUMBER_IOS,
416             "output-format": "json",
417             "pi_act": 0,
418             "apptag": "0x0888",
419             "apptag_mask": "0xFFFF",
420             },
421         "pi_chk": "GUARD",
422         "bs_low": BS_LOW,
423         "bs_high": BS_HIGH,
424         "test_class": DifDixTest,
425     },
426     {
427         # Read workload with variable IO sizes
428         # pi_act=1
429         # this case would trigger an apptag error, but pi_chk says to check
430         # only the Guard PI, so there should be no error
431         "test_id": 308,
432         "fio_opts": {
433             "rw": 'read',
434             "number_ios": NUMBER_IOS,
435             "output-format": "json",
436             "pi_act": 1,
437             "apptag": "0x0888",
438             "apptag_mask": "0xFFFF",
439             },
440         "pi_chk": "GUARD",
441         "bs_low": BS_LOW,
442         "bs_high": BS_HIGH,
443         "test_class": DifDixTest,
444     },
445     {
446         # Read workload with variable IO sizes
447         # pi_act=0
448         # this case would trigger an apptag error, but pi_chk says to check
449         # only the reftag, so there should be no error
450         # This case will be skipped when the device is formatted with Type 3 PI
451         # since Type 3 PI ignores the reftag
452         "test_id": 309,
453         "fio_opts": {
454             "rw": 'read',
455             "number_ios": NUMBER_IOS,
456             "output-format": "json",
457             "pi_act": 0,
458             "apptag": "0x0888",
459             "apptag_mask": "0xFFFF",
460             },
461         "pi_chk": "REFTAG",
462         "bs_low": BS_LOW,
463         "bs_high": BS_HIGH,
464         "skip": "type3",
465         "test_class": DifDixTest,
466     },
467     {
468         # Read workload with variable IO sizes
469         # pi_act=1
470         # this case would trigger an apptag error, but pi_chk says to check
471         # only the reftag, so there should be no error
472         # This case will be skipped when the device is formatted with Type 3 PI
473         # since Type 3 PI ignores the reftag
474         "test_id": 310,
475         "fio_opts": {
476             "rw": 'read',
477             "number_ios": NUMBER_IOS,
478             "output-format": "json",
479             "pi_act": 1,
480             "apptag": "0x0888",
481             "apptag_mask": "0xFFFF",
482             },
483         "pi_chk": "REFTAG",
484         "bs_low": BS_LOW,
485         "bs_high": BS_HIGH,
486         "skip": "type3",
487         "test_class": DifDixTest,
488     },
489     {
490         # Read workload with variable IO sizes
491         # pi_act=0
492         # use apptag mask to ignore apptag mismatch
493         "test_id": 311,
494         "fio_opts": {
495             "rw": 'read',
496             "number_ios": NUMBER_IOS,
497             "output-format": "json",
498             "pi_act": 0,
499             "apptag": "0x0888",
500             "apptag_mask": "0x0FFF",
501             },
502         "pi_chk": "APPTAG,GUARD,REFTAG",
503         "bs_low": BS_LOW,
504         "bs_high": BS_HIGH,
505         "test_class": DifDixTest,
506     },
507     {
508         # Read workload with variable IO sizes
509         # pi_act=1
510         # use apptag mask to ignore apptag mismatch
511         "test_id": 312,
512         "fio_opts": {
513             "rw": 'read',
514             "number_ios": NUMBER_IOS,
515             "output-format": "json",
516             "pi_act": 1,
517             "apptag": "0x0888",
518             "apptag_mask": "0x0FFF",
519             },
520         "pi_chk": "APPTAG,GUARD,REFTAG",
521         "bs_low": BS_LOW,
522         "bs_high": BS_HIGH,
523         "test_class": DifDixTest,
524     },
525     {
526         # Read workload with variable IO sizes
527         # pi_act=0
528         # use apptag mask to ignore apptag mismatch
529         "test_id": 313,
530         "fio_opts": {
531             "rw": 'read',
532             "number_ios": NUMBER_IOS,
533             "output-format": "json",
534             "pi_act": 0,
535             "apptag": "0xF888",
536             "apptag_mask": "0x0FFF",
537             },
538         "pi_chk": "APPTAG,GUARD,REFTAG",
539         "bs_low": BS_LOW,
540         "bs_high": BS_HIGH,
541         "test_class": DifDixTest,
542     },
543     {
544         # Read workload with variable IO sizes
545         # pi_act=1
546         # use apptag mask to ignore apptag mismatch
547         "test_id": 314,
548         "fio_opts": {
549             "rw": 'read',
550             "number_ios": NUMBER_IOS,
551             "output-format": "json",
552             "pi_act": 1,
553             "apptag": "0xF888",
554             "apptag_mask": "0x0FFF",
555             },
556         "pi_chk": "APPTAG,GUARD,REFTAG",
557         "bs_low": BS_LOW,
558         "bs_high": BS_HIGH,
559         "test_class": DifDixTest,
560     },
561     {
562         # Write workload with fixed large IO sizes
563         # Set apptag=0xFFFF to disable all checking for Type 1 and 2
564         # pi_act=1
565         "test_id": 315,
566         "fio_opts": {
567             "rw": 'write',
568             "number_ios": NUMBER_IOS,
569             "output-format": "json",
570             "apptag": "0xFFFF",
571             "apptag_mask": "0xFFFF",
572             "pi_act": 1,
573             },
574         "pi_chk": "APPTAG,GUARD,REFTAG",
575         "bs_low": BS_HIGH,
576         "bs_high": BS_HIGH,
577         "skip": "type3",
578         "test_class": DifDixTest,
579     },
580     {
581         # Read workload with variable IO sizes
582         # pi_act=0
583         # Data was written with apptag=0xFFFF
584         # Reading the data back should disable all checking for Type 1 and 2
585         "test_id": 316,
586         "fio_opts": {
587             "rw": 'read',
588             "number_ios": NUMBER_IOS,
589             "output-format": "json",
590             "pi_act": 0,
591             "apptag": "0x0101",
592             "apptag_mask": "0xFFFF",
593             },
594         "pi_chk": "APPTAG,GUARD,REFTAG",
595         "bs_low": BS_LOW,
596         "bs_high": BS_HIGH,
597         "skip": "type3",
598         "test_class": DifDixTest,
599     },
600     {
601         # Read workload with variable IO sizes
602         # pi_act=1
603         # Data was written with apptag=0xFFFF
604         # Reading the data back should disable all checking for Type 1 and 2
605         "test_id": 317,
606         "fio_opts": {
607             "rw": 'read',
608             "number_ios": NUMBER_IOS,
609             "output-format": "json",
610             "pi_act": 1,
611             "apptag": "0x0000",
612             "apptag_mask": "0xFFFF",
613             },
614         "pi_chk": "APPTAG,GUARD,REFTAG",
615         "bs_low": BS_LOW,
616         "bs_high": BS_HIGH,
617         "skip": "type3",
618         "test_class": DifDixTest,
619     },
620 #
621 # Error cases related to block size and metadata size
622 #
623     {
624         # Use a min block size that is not a multiple of lba/elba size to
625         # trigger an error.
626         "test_id": 401,
627         "fio_opts": {
628             "rw": 'read',
629             "number_ios": NUMBER_IOS,
630             "pi_act": 0,
631             "apptag": "0x8888",
632             "apptag_mask": "0x0FFF",
633             },
634         "pi_chk": "APPTAG,GUARD,REFTAG",
635         "bs_low": BS_LOW+0.5,
636         "bs_high": BS_HIGH,
637         "success": SUCCESS_NONZERO,
638         "test_class": DifDixTest,
639     },
640     {
641         # Use metadata size that is too small
642         "test_id": 402,
643         "fio_opts": {
644             "rw": 'read',
645             "number_ios": NUMBER_IOS,
646             "pi_act": 0,
647             "apptag": "0x8888",
648             "apptag_mask": "0x0FFF",
649             },
650         "pi_chk": "APPTAG,GUARD,REFTAG",
651         "bs_low": BS_LOW,
652         "bs_high": BS_HIGH,
653         "mdsize_adjustment": -1,
654         "success": SUCCESS_NONZERO,
655         "skip": "elba",
656         "test_class": DifDixTest,
657     },
658     {
659         # Read workload with variable IO sizes
660         # pi_act=0
661         # Should still work even if metadata size is too large
662         "test_id": 403,
663         "fio_opts": {
664             "rw": 'read',
665             "number_ios": NUMBER_IOS,
666             "pi_act": 0,
667             "apptag": "0x8888",
668             "apptag_mask": "0x0FFF",
669             },
670         "pi_chk": "APPTAG,GUARD,REFTAG",
671         "bs_low": BS_LOW,
672         "bs_high": BS_HIGH,
673         "mdsize_adjustment": 1,
674         "test_class": DifDixTest,
675     },
676 ]
677
678
679 def parse_args():
680     """Parse command-line arguments."""
681
682     parser = argparse.ArgumentParser()
683     parser.add_argument('-d', '--debug', help='Enable debug messages', action='store_true')
684     parser.add_argument('-f', '--fio', help='path to file executable (e.g., ./fio)')
685     parser.add_argument('-a', '--artifact-root', help='artifact root directory')
686     parser.add_argument('-s', '--skip', nargs='+', type=int,
687                         help='list of test(s) to skip')
688     parser.add_argument('-o', '--run-only', nargs='+', type=int,
689                         help='list of test(s) to run, skipping all others')
690     parser.add_argument('--dut', help='target NVMe character device to test '
691                         '(e.g., /dev/ng0n1). WARNING: THIS IS A DESTRUCTIVE TEST', required=True)
692     parser.add_argument('-l', '--lbaf', nargs='+', type=int,
693                         help='list of lba formats to test')
694     parser.add_argument('-i', '--ioengine', default='io_uring_cmd')
695     args = parser.parse_args()
696
697     return args
698
699
700 def get_lbafs(args):
701     """
702     Determine which LBA formats to use. Use either the ones specified on the
703     command line or if none are specified query the device and use all lba
704     formats with metadata.
705     """
706     lbaf_list = []
707     id_ns_cmd = f"sudo nvme id-ns --output-format=json {args.dut}".split(' ')
708     id_ns_output = subprocess.check_output(id_ns_cmd)
709     lbafs = json.loads(id_ns_output)['lbafs']
710     if args.lbaf:
711         for lbaf in args.lbaf:
712             lbaf_list.append({'lbaf': lbaf, 'ds': 2 ** lbafs[lbaf]['ds'],
713                               'ms': lbafs[lbaf]['ms'], })
714             if lbafs[lbaf]['ms'] == 0:
715                 print(f'Error: lbaf {lbaf} has metadata size zero')
716                 sys.exit(1)
717     else:
718         for lbaf_num, lbaf in enumerate(lbafs):
719             if lbaf['ms'] != 0:
720                 lbaf_list.append({'lbaf': lbaf_num, 'ds': 2 ** lbaf['ds'],
721                                   'ms': lbaf['ms'], })
722
723     return lbaf_list
724
725
726 def get_guard_pi(lbaf_list, args):
727     """
728     Find out how many bits of guard protection information are associated with
729     each lbaf to be used. If this is not available assume 16-bit guard pi.
730     Also record the bytes of protection information associated with the number
731     of guard PI bits.
732     """
733     nvm_id_ns_cmd = f"sudo nvme nvm-id-ns --output-format=json {args.dut}".split(' ')
734     try:
735         nvm_id_ns_output = subprocess.check_output(nvm_id_ns_cmd)
736     except subprocess.CalledProcessError:
737         print(f"Non-zero return code from {' '.join(nvm_id_ns_cmd)}; " \
738                 "assuming all lbafs use 16b Guard Protection Information")
739         for lbaf in lbaf_list:
740             lbaf['guard_pi_bits'] = 16
741     else:
742         elbafs = json.loads(nvm_id_ns_output)['elbafs']
743         for elbaf_num, elbaf in enumerate(elbafs):
744             for lbaf in lbaf_list:
745                 if lbaf['lbaf'] == elbaf_num:
746                     lbaf['guard_pi_bits'] = 16 << elbaf['pif']
747
748     # For 16b Guard Protection Information, the PI requires 8 bytes
749     # For 32b and 64b Guard PI, the PI requires 16 bytes
750     for lbaf in lbaf_list:
751         if lbaf['guard_pi_bits'] == 16:
752             lbaf['pi_bytes'] = 8
753         else:
754             lbaf['pi_bytes'] = 16
755
756
757 def get_capabilities(args):
758     """
759     Determine what end-to-end data protection features the device supports.
760     """
761     caps = { 'pil': [], 'pitype': [], 'elba': [] }
762     id_ns_cmd = f"sudo nvme id-ns --output-format=json {args.dut}".split(' ')
763     id_ns_output = subprocess.check_output(id_ns_cmd)
764     id_ns_json = json.loads(id_ns_output)
765
766     mc = id_ns_json['mc']
767     if mc & 1:
768         caps['elba'].append(1)
769     if mc & 2:
770         caps['elba'].append(0)
771
772     dpc = id_ns_json['dpc']
773     if dpc & 1:
774         caps['pitype'].append(1)
775     if dpc & 2:
776         caps['pitype'].append(2)
777     if dpc & 4:
778         caps['pitype'].append(3)
779     if dpc & 8:
780         caps['pil'].append(1)
781     if dpc & 16:
782         caps['pil'].append(0)
783
784     for _, value in caps.items():
785         if len(value) == 0:
786             logging.error("One or more end-to-end data protection features unsupported: %s", caps)
787             sys.exit(-1)
788
789     return caps
790
791
792 def format_device(args, lbaf, pitype, pil, elba):
793     """
794     Format device using specified lba format with specified pitype, pil, and
795     elba values.
796     """
797
798     format_cmd = f"sudo nvme format {args.dut} --lbaf={lbaf['lbaf']} " \
799                  f"--pi={pitype} --pil={pil} --ms={elba} --force"
800     logging.debug("Format command: %s", format_cmd)
801     format_cmd = format_cmd.split(' ')
802     format_cmd_result = subprocess.run(format_cmd, capture_output=True, check=False,
803                                        encoding=locale.getpreferredencoding())
804
805     # Sometimes nvme-cli may format the device successfully but fail to
806     # rescan the namespaces after the format. Continue if this happens but
807     # abort if some other error occurs.
808     if format_cmd_result.returncode != 0:
809         if 'failed to rescan namespaces' not in format_cmd_result.stderr \
810                 or 'Success formatting namespace' not in format_cmd_result.stdout:
811             logging.error(format_cmd_result.stdout)
812             logging.error(format_cmd_result.stderr)
813             print("Unable to format device; skipping this configuration")
814             return False
815
816     logging.debug(format_cmd_result.stdout)
817     return True
818
819
820 def difdix_test(test_env, args, lbaf, pitype, elba):
821     """
822     Adjust test arguments based on values of lbaf, pitype, and elba.  Then run
823     the tests.
824     """
825     for test in TEST_LIST:
826         test['force_skip'] = False
827
828         blocksize = lbaf['ds']
829         # Set fio blocksize parameter at runtime
830         # If we formatted the device in extended LBA mode (e.g., 520-byte
831         # sectors), we usually need to add the lba data size and metadata size
832         # together for fio's bs parameter. However, if pi_act == 1 and the
833         # device is formatted so that the metadata is the same size as the PI,
834         # then the device will take care of everything and the application
835         # should just use regular power of 2 lba data size even when the device
836         # is in extended lba mode.
837         if elba:
838             if not test['fio_opts']['pi_act'] or lbaf['ms'] != lbaf['pi_bytes']:
839                 blocksize += lbaf['ms']
840             test['fio_opts']['md_per_io_size'] = 0
841         else:
842         # If we are using a separate buffer for metadata, fio doesn't need to
843         # do anything when pi_act==1 and protection information size is equal to
844         # metadata size since the device is taking care of it all. If either of
845         # the two conditions do not hold, then we do need to allocate a
846         # separate metadata buffer.
847             if test['fio_opts']['pi_act'] and lbaf['ms'] == lbaf['pi_bytes']:
848                 test['fio_opts']['md_per_io_size'] = 0
849             else:
850                 test['fio_opts']['md_per_io_size'] = lbaf['ms'] * test['bs_high']
851
852         test['fio_opts']['bsrange'] = f"{blocksize * test['bs_low']}-{blocksize * test['bs_high']}"
853         if 'mdsize_adjustment' in test:
854             test['fio_opts']['md_per_io_size'] += test['mdsize_adjustment']
855
856         # Set fio pi_chk parameter at runtime. If the device is formatted
857         # with Type 3 protection information, this means that the reference
858         # tag is not checked and I/O commands may throw an error if they
859         # are submitted with the REFTAG bit set in pi_chk. Make sure fio
860         # does not set pi_chk's REFTAG bit if the device is formatted with
861         # Type 3 PI.
862         if 'pi_chk' in test:
863             if pitype == 3 and 'REFTAG' in test['pi_chk']:
864                 test['fio_opts']['pi_chk'] = test['pi_chk'].replace('REFTAG','')
865                 logging.debug("Type 3 PI: dropping REFTAG bit")
866             else:
867                 test['fio_opts']['pi_chk'] = test['pi_chk']
868
869         if 'skip' in test:
870             if pitype == 3 and 'type3' in test['skip']:
871                 test['force_skip'] = True
872                 logging.debug("Type 3 PI: skipping test case")
873             if elba and 'elba' in test['skip']:
874                 test['force_skip'] = True
875                 logging.debug("extended lba format: skipping test case")
876
877         logging.debug("Test %d: pi_act=%d, bsrange=%s, md_per_io_size=%d", test['test_id'],
878                       test['fio_opts']['pi_act'], test['fio_opts']['bsrange'],
879                       test['fio_opts']['md_per_io_size'])
880
881     return run_fio_tests(TEST_LIST, test_env, args)
882
883
884 def main():
885     """
886     Run tests using fio's io_uring_cmd ioengine to exercise end-to-end data
887     protection capabilities.
888     """
889
890     args = parse_args()
891
892     if args.debug:
893         logging.basicConfig(level=logging.DEBUG)
894     else:
895         logging.basicConfig(level=logging.INFO)
896
897     artifact_root = args.artifact_root if args.artifact_root else \
898         f"nvmept_pi-test-{time.strftime('%Y%m%d-%H%M%S')}"
899     os.mkdir(artifact_root)
900     print(f"Artifact directory is {artifact_root}")
901
902     if args.fio:
903         fio_path = str(Path(args.fio).absolute())
904     else:
905         fio_path = 'fio'
906     print(f"fio path is {fio_path}")
907
908     lbaf_list = get_lbafs(args)
909     get_guard_pi(lbaf_list, args)
910     caps = get_capabilities(args)
911     print("Device capabilities:", caps)
912
913     for test in TEST_LIST:
914         test['fio_opts']['filename'] = args.dut
915         test['fio_opts']['ioengine'] = args.ioengine
916
917     test_env = {
918               'fio_path': fio_path,
919               'fio_root': str(Path(__file__).absolute().parent.parent),
920               'artifact_root': artifact_root,
921               'basename': 'nvmept_pi',
922               }
923
924     total = { 'passed':  0, 'failed': 0, 'skipped': 0 }
925
926     try:
927         for lbaf, pil, pitype, elba in itertools.product(lbaf_list, caps['pil'], caps['pitype'],
928                                                          caps['elba']):
929             print(f"\nlbaf: {lbaf}, pil: {pil}, pitype: {pitype}, elba: {elba}")
930
931             if not format_device(args, lbaf, pitype, pil, elba):
932                 continue
933
934             test_env['artifact_root'] = \
935                 os.path.join(artifact_root, f"lbaf{lbaf['lbaf']}pil{pil}pitype{pitype}" \
936                     f"elba{elba}")
937             os.mkdir(test_env['artifact_root'])
938
939             passed, failed, skipped = difdix_test(test_env, args, lbaf, pitype, elba)
940
941             total['passed'] += passed
942             total['failed'] += failed
943             total['skipped'] += skipped
944     except KeyboardInterrupt:
945         pass
946
947     print(f"\n\n{total['passed']} test(s) passed, {total['failed']} failed, " \
948             f"{total['skipped']} skipped")
949     sys.exit(total['failed'])
950
951
952 if __name__ == '__main__':
953     main()