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