qemu

FORK: QEMU emulator
git clone https://git.neptards.moe/neptards/qemu.git
Log | Files | Refs | Submodules | LICENSE

fdc-test.c (15432B)


      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 }