Commit | Line | Data |
---|---|---|
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 | """ | |
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() |