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