docs: change listed type for log_window_value to str
[fio.git] / t / nvmept_pi.py
CommitLineData
d0f7d9fe
VF
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"""
19import os
20import sys
21import json
22import time
23import locale
24import logging
25import argparse
26import itertools
27import subprocess
28from pathlib import Path
29from fiotestlib import FioJobCmdTest, run_fio_tests
30from fiotestcommon import SUCCESS_NONZERO
31
32NUMBER_IOS = 8192
33BS_LOW = 1
34BS_HIGH = 16
35
36class 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
69TEST_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
677def 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
697def 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
723def 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
754def 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
789def 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
817def 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
881def 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
948if __name__ == '__main__':
949 main()