xref: /qemu/tests/qtest/fdc-test.c (revision 75ac231c)
1 /*
2  * Floppy test cases.
3  *
4  * Copyright (c) 2012 Kevin Wolf <kwolf@redhat.com>
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to deal
8  * in the Software without restriction, including without limitation the rights
9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10  * copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22  * THE SOFTWARE.
23  */
24 
25 #include "qemu/osdep.h"
26 
27 
28 #include "libqtest-single.h"
29 #include "qapi/qmp/qdict.h"
30 
31 /* TODO actually test the results and get rid of this */
32 #define qmp_discard_response(...) qobject_unref(qmp(__VA_ARGS__))
33 
34 #define DRIVE_FLOPPY_BLANK \
35     "-drive if=floppy,file=null-co://,file.read-zeroes=on,format=raw,size=1440k"
36 
37 #define TEST_IMAGE_SIZE 1440 * 1024
38 
39 #define FLOPPY_BASE 0x3f0
40 #define FLOPPY_IRQ 6
41 
42 enum {
43     reg_sra         = 0x0,
44     reg_srb         = 0x1,
45     reg_dor         = 0x2,
46     reg_msr         = 0x4,
47     reg_dsr         = 0x4,
48     reg_fifo        = 0x5,
49     reg_dir         = 0x7,
50 };
51 
52 enum {
53     CMD_SENSE_INT           = 0x08,
54     CMD_READ_ID             = 0x0a,
55     CMD_SEEK                = 0x0f,
56     CMD_VERIFY              = 0x16,
57     CMD_READ                = 0xe6,
58     CMD_RELATIVE_SEEK_OUT   = 0x8f,
59     CMD_RELATIVE_SEEK_IN    = 0xcf,
60 };
61 
62 enum {
63     BUSY    = 0x10,
64     NONDMA  = 0x20,
65     RQM     = 0x80,
66     DIO     = 0x40,
67 
68     DSKCHG  = 0x80,
69 };
70 
71 static char *test_image;
72 
73 #define assert_bit_set(data, mask) g_assert_cmphex((data) & (mask), ==, (mask))
74 #define assert_bit_clear(data, mask) g_assert_cmphex((data) & (mask), ==, 0)
75 
76 static uint8_t base = 0x70;
77 
78 enum {
79     CMOS_FLOPPY     = 0x10,
80 };
81 
82 static void floppy_send(uint8_t byte)
83 {
84     uint8_t msr;
85 
86     msr = inb(FLOPPY_BASE + reg_msr);
87     assert_bit_set(msr, RQM);
88     assert_bit_clear(msr, DIO);
89 
90     outb(FLOPPY_BASE + reg_fifo, byte);
91 }
92 
93 static uint8_t floppy_recv(void)
94 {
95     uint8_t msr;
96 
97     msr = inb(FLOPPY_BASE + reg_msr);
98     assert_bit_set(msr, RQM | DIO);
99 
100     return inb(FLOPPY_BASE + reg_fifo);
101 }
102 
103 /* pcn: Present Cylinder Number */
104 static void ack_irq(uint8_t *pcn)
105 {
106     uint8_t ret;
107 
108     g_assert(get_irq(FLOPPY_IRQ));
109     floppy_send(CMD_SENSE_INT);
110     floppy_recv();
111 
112     ret = floppy_recv();
113     if (pcn != NULL) {
114         *pcn = ret;
115     }
116 
117     g_assert(!get_irq(FLOPPY_IRQ));
118 }
119 
120 static uint8_t send_read_command(uint8_t cmd)
121 {
122     uint8_t drive = 0;
123     uint8_t head = 0;
124     uint8_t cyl = 0;
125     uint8_t sect_addr = 1;
126     uint8_t sect_size = 2;
127     uint8_t eot = 1;
128     uint8_t gap = 0x1b;
129     uint8_t gpl = 0xff;
130 
131     uint8_t msr = 0;
132     uint8_t st0;
133 
134     uint8_t ret = 0;
135 
136     floppy_send(cmd);
137     floppy_send(head << 2 | drive);
138     g_assert(!get_irq(FLOPPY_IRQ));
139     floppy_send(cyl);
140     floppy_send(head);
141     floppy_send(sect_addr);
142     floppy_send(sect_size);
143     floppy_send(eot);
144     floppy_send(gap);
145     floppy_send(gpl);
146 
147     uint8_t i = 0;
148     uint8_t n = 2;
149     for (; i < n; i++) {
150         msr = inb(FLOPPY_BASE + reg_msr);
151         if (msr == 0xd0) {
152             break;
153         }
154         sleep(1);
155     }
156 
157     if (i >= n) {
158         return 1;
159     }
160 
161     st0 = floppy_recv();
162     if (st0 != 0x40) {
163         ret = 1;
164     }
165 
166     floppy_recv();
167     floppy_recv();
168     floppy_recv();
169     floppy_recv();
170     floppy_recv();
171     floppy_recv();
172 
173     return ret;
174 }
175 
176 static uint8_t send_read_no_dma_command(int nb_sect, uint8_t expected_st0)
177 {
178     uint8_t drive = 0;
179     uint8_t head = 0;
180     uint8_t cyl = 0;
181     uint8_t sect_addr = 1;
182     uint8_t sect_size = 2;
183     uint8_t eot = nb_sect;
184     uint8_t gap = 0x1b;
185     uint8_t gpl = 0xff;
186 
187     uint8_t msr = 0;
188     uint8_t st0;
189 
190     uint8_t ret = 0;
191 
192     floppy_send(CMD_READ);
193     floppy_send(head << 2 | drive);
194     g_assert(!get_irq(FLOPPY_IRQ));
195     floppy_send(cyl);
196     floppy_send(head);
197     floppy_send(sect_addr);
198     floppy_send(sect_size);
199     floppy_send(eot);
200     floppy_send(gap);
201     floppy_send(gpl);
202 
203     uint16_t i = 0;
204     uint8_t n = 2;
205     for (; i < n; i++) {
206         msr = inb(FLOPPY_BASE + reg_msr);
207         if (msr == (BUSY | NONDMA | DIO | RQM)) {
208             break;
209         }
210         sleep(1);
211     }
212 
213     if (i >= n) {
214         return 1;
215     }
216 
217     /* Non-DMA mode */
218     for (i = 0; i < 512 * 2 * nb_sect; i++) {
219         msr = inb(FLOPPY_BASE + reg_msr);
220         assert_bit_set(msr, BUSY | RQM | DIO);
221         inb(FLOPPY_BASE + reg_fifo);
222     }
223 
224     msr = inb(FLOPPY_BASE + reg_msr);
225     assert_bit_set(msr, BUSY | RQM | DIO);
226     g_assert(get_irq(FLOPPY_IRQ));
227 
228     st0 = floppy_recv();
229     if (st0 != expected_st0) {
230         ret = 1;
231     }
232 
233     floppy_recv();
234     floppy_recv();
235     floppy_recv();
236     floppy_recv();
237     floppy_recv();
238     g_assert(get_irq(FLOPPY_IRQ));
239     floppy_recv();
240 
241     /* Check that we're back in command phase */
242     msr = inb(FLOPPY_BASE + reg_msr);
243     assert_bit_clear(msr, BUSY | DIO);
244     assert_bit_set(msr, RQM);
245     g_assert(!get_irq(FLOPPY_IRQ));
246 
247     return ret;
248 }
249 
250 static void send_seek(int cyl)
251 {
252     int drive = 0;
253     int head = 0;
254 
255     floppy_send(CMD_SEEK);
256     floppy_send(head << 2 | drive);
257     g_assert(!get_irq(FLOPPY_IRQ));
258     floppy_send(cyl);
259     ack_irq(NULL);
260 }
261 
262 static uint8_t cmos_read(uint8_t reg)
263 {
264     outb(base + 0, reg);
265     return inb(base + 1);
266 }
267 
268 static void test_cmos(void)
269 {
270     uint8_t cmos;
271 
272     cmos = cmos_read(CMOS_FLOPPY);
273     g_assert(cmos == 0x40 || cmos == 0x50);
274 }
275 
276 static void test_no_media_on_start(void)
277 {
278     uint8_t dir;
279 
280     /* Media changed bit must be set all time after start if there is
281      * no media in drive. */
282     dir = inb(FLOPPY_BASE + reg_dir);
283     assert_bit_set(dir, DSKCHG);
284     dir = inb(FLOPPY_BASE + reg_dir);
285     assert_bit_set(dir, DSKCHG);
286     send_seek(1);
287     dir = inb(FLOPPY_BASE + reg_dir);
288     assert_bit_set(dir, DSKCHG);
289     dir = inb(FLOPPY_BASE + reg_dir);
290     assert_bit_set(dir, DSKCHG);
291 }
292 
293 static void test_read_without_media(void)
294 {
295     uint8_t ret;
296 
297     ret = send_read_command(CMD_READ);
298     g_assert(ret == 0);
299 }
300 
301 static void test_media_insert(void)
302 {
303     uint8_t dir;
304 
305     /* Insert media in drive. DSKCHK should not be reset until a step pulse
306      * is sent. */
307     qmp_discard_response("{'execute':'blockdev-change-medium', 'arguments':{"
308                          " 'id':'floppy0', 'filename': %s, 'format': 'raw' }}",
309                          test_image);
310 
311     dir = inb(FLOPPY_BASE + reg_dir);
312     assert_bit_set(dir, DSKCHG);
313     dir = inb(FLOPPY_BASE + reg_dir);
314     assert_bit_set(dir, DSKCHG);
315 
316     send_seek(0);
317     dir = inb(FLOPPY_BASE + reg_dir);
318     assert_bit_set(dir, DSKCHG);
319     dir = inb(FLOPPY_BASE + reg_dir);
320     assert_bit_set(dir, DSKCHG);
321 
322     /* Step to next track should clear DSKCHG bit. */
323     send_seek(1);
324     dir = inb(FLOPPY_BASE + reg_dir);
325     assert_bit_clear(dir, DSKCHG);
326     dir = inb(FLOPPY_BASE + reg_dir);
327     assert_bit_clear(dir, DSKCHG);
328 }
329 
330 static void test_media_change(void)
331 {
332     uint8_t dir;
333 
334     test_media_insert();
335 
336     /* Eject the floppy and check that DSKCHG is set. Reading it out doesn't
337      * reset the bit. */
338     qmp_discard_response("{'execute':'eject', 'arguments':{"
339                          " 'id':'floppy0' }}");
340 
341     dir = inb(FLOPPY_BASE + reg_dir);
342     assert_bit_set(dir, DSKCHG);
343     dir = inb(FLOPPY_BASE + reg_dir);
344     assert_bit_set(dir, DSKCHG);
345 
346     send_seek(0);
347     dir = inb(FLOPPY_BASE + reg_dir);
348     assert_bit_set(dir, DSKCHG);
349     dir = inb(FLOPPY_BASE + reg_dir);
350     assert_bit_set(dir, DSKCHG);
351 
352     send_seek(1);
353     dir = inb(FLOPPY_BASE + reg_dir);
354     assert_bit_set(dir, DSKCHG);
355     dir = inb(FLOPPY_BASE + reg_dir);
356     assert_bit_set(dir, DSKCHG);
357 }
358 
359 static void test_sense_interrupt(void)
360 {
361     int drive = 0;
362     int head = 0;
363     int cyl = 0;
364     int ret = 0;
365 
366     floppy_send(CMD_SENSE_INT);
367     ret = floppy_recv();
368     g_assert(ret == 0x80);
369 
370     floppy_send(CMD_SEEK);
371     floppy_send(head << 2 | drive);
372     g_assert(!get_irq(FLOPPY_IRQ));
373     floppy_send(cyl);
374 
375     floppy_send(CMD_SENSE_INT);
376     ret = floppy_recv();
377     g_assert(ret == 0x20);
378     floppy_recv();
379 }
380 
381 static void test_relative_seek(void)
382 {
383     uint8_t drive = 0;
384     uint8_t head = 0;
385     uint8_t cyl = 1;
386     uint8_t pcn;
387 
388     /* Send seek to track 0 */
389     send_seek(0);
390 
391     /* Send relative seek to increase track by 1 */
392     floppy_send(CMD_RELATIVE_SEEK_IN);
393     floppy_send(head << 2 | drive);
394     g_assert(!get_irq(FLOPPY_IRQ));
395     floppy_send(cyl);
396 
397     ack_irq(&pcn);
398     g_assert(pcn == 1);
399 
400     /* Send relative seek to decrease track by 1 */
401     floppy_send(CMD_RELATIVE_SEEK_OUT);
402     floppy_send(head << 2 | drive);
403     g_assert(!get_irq(FLOPPY_IRQ));
404     floppy_send(cyl);
405 
406     ack_irq(&pcn);
407     g_assert(pcn == 0);
408 }
409 
410 static void test_read_id(void)
411 {
412     uint8_t drive = 0;
413     uint8_t head = 0;
414     uint8_t cyl;
415     uint8_t st0;
416     uint8_t msr;
417 
418     /* Seek to track 0 and check with READ ID */
419     send_seek(0);
420 
421     floppy_send(CMD_READ_ID);
422     g_assert(!get_irq(FLOPPY_IRQ));
423     floppy_send(head << 2 | drive);
424 
425     msr = inb(FLOPPY_BASE + reg_msr);
426     if (!get_irq(FLOPPY_IRQ)) {
427         assert_bit_set(msr, BUSY);
428         assert_bit_clear(msr, RQM);
429     }
430 
431     while (!get_irq(FLOPPY_IRQ)) {
432         /* qemu involves a timer with READ ID... */
433         clock_step(1000000000LL / 50);
434     }
435 
436     msr = inb(FLOPPY_BASE + reg_msr);
437     assert_bit_set(msr, BUSY | RQM | DIO);
438 
439     st0 = floppy_recv();
440     floppy_recv();
441     floppy_recv();
442     cyl = floppy_recv();
443     head = floppy_recv();
444     floppy_recv();
445     g_assert(get_irq(FLOPPY_IRQ));
446     floppy_recv();
447     g_assert(!get_irq(FLOPPY_IRQ));
448 
449     g_assert_cmpint(cyl, ==, 0);
450     g_assert_cmpint(head, ==, 0);
451     g_assert_cmpint(st0, ==, head << 2);
452 
453     /* Seek to track 8 on head 1 and check with READ ID */
454     head = 1;
455     cyl = 8;
456 
457     floppy_send(CMD_SEEK);
458     floppy_send(head << 2 | drive);
459     g_assert(!get_irq(FLOPPY_IRQ));
460     floppy_send(cyl);
461     g_assert(get_irq(FLOPPY_IRQ));
462     ack_irq(NULL);
463 
464     floppy_send(CMD_READ_ID);
465     g_assert(!get_irq(FLOPPY_IRQ));
466     floppy_send(head << 2 | drive);
467 
468     msr = inb(FLOPPY_BASE + reg_msr);
469     if (!get_irq(FLOPPY_IRQ)) {
470         assert_bit_set(msr, BUSY);
471         assert_bit_clear(msr, RQM);
472     }
473 
474     while (!get_irq(FLOPPY_IRQ)) {
475         /* qemu involves a timer with READ ID... */
476         clock_step(1000000000LL / 50);
477     }
478 
479     msr = inb(FLOPPY_BASE + reg_msr);
480     assert_bit_set(msr, BUSY | RQM | DIO);
481 
482     st0 = floppy_recv();
483     floppy_recv();
484     floppy_recv();
485     cyl = floppy_recv();
486     head = floppy_recv();
487     floppy_recv();
488     g_assert(get_irq(FLOPPY_IRQ));
489     floppy_recv();
490     g_assert(!get_irq(FLOPPY_IRQ));
491 
492     g_assert_cmpint(cyl, ==, 8);
493     g_assert_cmpint(head, ==, 1);
494     g_assert_cmpint(st0, ==, head << 2);
495 }
496 
497 static void test_read_no_dma_1(void)
498 {
499     uint8_t ret;
500 
501     outb(FLOPPY_BASE + reg_dor, inb(FLOPPY_BASE + reg_dor) & ~0x08);
502     send_seek(0);
503     ret = send_read_no_dma_command(1, 0x04);
504     g_assert(ret == 0);
505 }
506 
507 static void test_read_no_dma_18(void)
508 {
509     uint8_t ret;
510 
511     outb(FLOPPY_BASE + reg_dor, inb(FLOPPY_BASE + reg_dor) & ~0x08);
512     send_seek(0);
513     ret = send_read_no_dma_command(18, 0x04);
514     g_assert(ret == 0);
515 }
516 
517 static void test_read_no_dma_19(void)
518 {
519     uint8_t ret;
520 
521     outb(FLOPPY_BASE + reg_dor, inb(FLOPPY_BASE + reg_dor) & ~0x08);
522     send_seek(0);
523     ret = send_read_no_dma_command(19, 0x20);
524     g_assert(ret == 0);
525 }
526 
527 static void test_verify(void)
528 {
529     uint8_t ret;
530 
531     ret = send_read_command(CMD_VERIFY);
532     g_assert(ret == 0);
533 }
534 
535 /* success if no crash or abort */
536 static void fuzz_registers(void)
537 {
538     unsigned int i;
539 
540     for (i = 0; i < 1000; i++) {
541         uint8_t reg, val;
542 
543         reg = (uint8_t)g_test_rand_int_range(0, 8);
544         val = (uint8_t)g_test_rand_int_range(0, 256);
545 
546         outb(FLOPPY_BASE + reg, val);
547         inb(FLOPPY_BASE + reg);
548     }
549 }
550 
551 static bool qtest_check_clang_sanitizer(void)
552 {
553 #ifdef QEMU_SANITIZE_ADDRESS
554     return true;
555 #else
556     g_test_skip("QEMU not configured using --enable-sanitizers");
557     return false;
558 #endif
559 }
560 static void test_cve_2021_20196(void)
561 {
562     QTestState *s;
563 
564     if (!qtest_check_clang_sanitizer()) {
565         return;
566     }
567 
568     s = qtest_initf("-nographic -m 32M -nodefaults " DRIVE_FLOPPY_BLANK);
569 
570     qtest_outw(s, 0x3f4, 0x0500);
571     qtest_outb(s, 0x3f5, 0x00);
572     qtest_outb(s, 0x3f5, 0x00);
573     qtest_outw(s, 0x3f4, 0x0000);
574     qtest_outb(s, 0x3f5, 0x00);
575     qtest_outw(s, 0x3f1, 0x0400);
576     qtest_outw(s, 0x3f4, 0x0000);
577     qtest_outw(s, 0x3f4, 0x0000);
578     qtest_outb(s, 0x3f5, 0x00);
579     qtest_outb(s, 0x3f5, 0x01);
580     qtest_outw(s, 0x3f1, 0x0500);
581     qtest_outb(s, 0x3f5, 0x00);
582     qtest_quit(s);
583 }
584 
585 static void test_cve_2021_3507(void)
586 {
587     QTestState *s;
588 
589     s = qtest_initf("-nographic -m 32M -nodefaults "
590                     "-drive file=%s,format=raw,if=floppy,snapshot=on",
591                     test_image);
592     qtest_outl(s, 0x9, 0x0a0206);
593     qtest_outw(s, 0x3f4, 0x1600);
594     qtest_outw(s, 0x3f4, 0x0000);
595     qtest_outw(s, 0x3f4, 0x0000);
596     qtest_outw(s, 0x3f4, 0x0000);
597     qtest_outw(s, 0x3f4, 0x0200);
598     qtest_outw(s, 0x3f4, 0x0200);
599     qtest_outw(s, 0x3f4, 0x0000);
600     qtest_outw(s, 0x3f4, 0x0000);
601     qtest_outw(s, 0x3f4, 0x0000);
602     qtest_quit(s);
603 }
604 
605 int main(int argc, char **argv)
606 {
607     int fd;
608     int ret;
609 
610     /* Create a temporary raw image */
611     fd = g_file_open_tmp("qtest.XXXXXX", &test_image, NULL);
612     g_assert(fd >= 0);
613     ret = ftruncate(fd, TEST_IMAGE_SIZE);
614     g_assert(ret == 0);
615     close(fd);
616 
617     /* Run the tests */
618     g_test_init(&argc, &argv, NULL);
619 
620     qtest_start("-machine pc -device floppy,id=floppy0");
621     qtest_irq_intercept_in(global_qtest, "ioapic");
622     qtest_add_func("/fdc/cmos", test_cmos);
623     qtest_add_func("/fdc/no_media_on_start", test_no_media_on_start);
624     qtest_add_func("/fdc/read_without_media", test_read_without_media);
625     qtest_add_func("/fdc/media_change", test_media_change);
626     qtest_add_func("/fdc/sense_interrupt", test_sense_interrupt);
627     qtest_add_func("/fdc/relative_seek", test_relative_seek);
628     qtest_add_func("/fdc/read_id", test_read_id);
629     qtest_add_func("/fdc/verify", test_verify);
630     qtest_add_func("/fdc/media_insert", test_media_insert);
631     qtest_add_func("/fdc/read_no_dma_1", test_read_no_dma_1);
632     qtest_add_func("/fdc/read_no_dma_18", test_read_no_dma_18);
633     qtest_add_func("/fdc/read_no_dma_19", test_read_no_dma_19);
634     qtest_add_func("/fdc/fuzz-registers", fuzz_registers);
635     qtest_add_func("/fdc/fuzz/cve_2021_20196", test_cve_2021_20196);
636     qtest_add_func("/fdc/fuzz/cve_2021_3507", test_cve_2021_3507);
637 
638     ret = g_test_run();
639 
640     /* Cleanup */
641     qtest_end();
642     unlink(test_image);
643     g_free(test_image);
644 
645     return ret;
646 }
647