Merge branch 'master' of https://github.com/celestinechen/fio
[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",
b140fc5e 46 f"--ioengine={self.fio_opts['ioengine']}",
d0f7d9fe
VF
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']}",
d0f7d9fe
VF
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',
450cc0c7
VF
59 'time_based', 'runtime', 'verify', 'io_size', 'offset', 'number_ios',
60 'output-format']:
d0f7d9fe
VF
61 if opt in self.fio_opts:
62 option = f"--{opt}={self.fio_opts[opt]}"
63 fio_args.append(option)
64
b140fc5e
VF
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
d0f7d9fe
VF
71 super().setup(fio_args)
72
73
74TEST_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,
d0f7d9fe
VF
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,
d0f7d9fe
VF
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,
d0f7d9fe
VF
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
679def 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')
b140fc5e 694 parser.add_argument('-i', '--ioengine', default='io_uring_cmd')
d0f7d9fe
VF
695 args = parser.parse_args()
696
697 return args
698
699
700def 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
726def 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
757def 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
792def 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
820def 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
884def 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
b140fc5e 915 test['fio_opts']['ioengine'] = args.ioengine
d0f7d9fe
VF
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
952if __name__ == '__main__':
953 main()