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