xref: /qemu/tests/qtest/fdc-test.c (revision 638466f777dc385dbd9b581ea31c6defd5f75efe)
193e9eb68SKevin Wolf /*
293e9eb68SKevin Wolf  * Floppy test cases.
393e9eb68SKevin Wolf  *
493e9eb68SKevin Wolf  * Copyright (c) 2012 Kevin Wolf <kwolf@redhat.com>
593e9eb68SKevin Wolf  *
693e9eb68SKevin Wolf  * Permission is hereby granted, free of charge, to any person obtaining a copy
793e9eb68SKevin Wolf  * of this software and associated documentation files (the "Software"), to deal
893e9eb68SKevin Wolf  * in the Software without restriction, including without limitation the rights
993e9eb68SKevin Wolf  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1093e9eb68SKevin Wolf  * copies of the Software, and to permit persons to whom the Software is
1193e9eb68SKevin Wolf  * furnished to do so, subject to the following conditions:
1293e9eb68SKevin Wolf  *
1393e9eb68SKevin Wolf  * The above copyright notice and this permission notice shall be included in
1493e9eb68SKevin Wolf  * all copies or substantial portions of the Software.
1593e9eb68SKevin Wolf  *
1693e9eb68SKevin Wolf  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1793e9eb68SKevin Wolf  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1893e9eb68SKevin Wolf  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
1993e9eb68SKevin Wolf  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
2093e9eb68SKevin Wolf  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2193e9eb68SKevin Wolf  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
2293e9eb68SKevin Wolf  * THE SOFTWARE.
2393e9eb68SKevin Wolf  */
2493e9eb68SKevin Wolf 
25681c28a3SPeter Maydell #include "qemu/osdep.h"
2693e9eb68SKevin Wolf 
2793e9eb68SKevin Wolf 
28dd210749SThomas Huth #include "libqtest-single.h"
29055a1efcSMarkus Armbruster #include "qapi/qmp/qdict.h"
3093e9eb68SKevin Wolf 
31055a1efcSMarkus Armbruster /* TODO actually test the results and get rid of this */
32055a1efcSMarkus Armbruster #define qmp_discard_response(...) qobject_unref(qmp(__VA_ARGS__))
33055a1efcSMarkus Armbruster 
34cc20926eSPhilippe Mathieu-Daudé #define DRIVE_FLOPPY_BLANK \
35cc20926eSPhilippe Mathieu-Daudé     "-drive if=floppy,file=null-co://,file.read-zeroes=on,format=raw,size=1440k"
36cc20926eSPhilippe Mathieu-Daudé 
3793e9eb68SKevin Wolf #define TEST_IMAGE_SIZE 1440 * 1024
3893e9eb68SKevin Wolf 
3993e9eb68SKevin Wolf #define FLOPPY_BASE 0x3f0
4093e9eb68SKevin Wolf #define FLOPPY_IRQ 6
4193e9eb68SKevin Wolf 
4293e9eb68SKevin Wolf enum {
4393e9eb68SKevin Wolf     reg_sra         = 0x0,
4493e9eb68SKevin Wolf     reg_srb         = 0x1,
4593e9eb68SKevin Wolf     reg_dor         = 0x2,
4693e9eb68SKevin Wolf     reg_msr         = 0x4,
4793e9eb68SKevin Wolf     reg_dsr         = 0x4,
4893e9eb68SKevin Wolf     reg_fifo        = 0x5,
4993e9eb68SKevin Wolf     reg_dir         = 0x7,
5093e9eb68SKevin Wolf };
5193e9eb68SKevin Wolf 
5293e9eb68SKevin Wolf enum {
5393e9eb68SKevin Wolf     CMD_SENSE_INT           = 0x08,
5467f194bdSKevin Wolf     CMD_READ_ID             = 0x0a,
5593e9eb68SKevin Wolf     CMD_SEEK                = 0x0f,
566f442fe8SHervé Poussineau     CMD_VERIFY              = 0x16,
578b9ef60dSPavel Hrdina     CMD_READ                = 0xe6,
5898272dbbSPavel Hrdina     CMD_RELATIVE_SEEK_OUT   = 0x8f,
5998272dbbSPavel Hrdina     CMD_RELATIVE_SEEK_IN    = 0xcf,
6093e9eb68SKevin Wolf };
6193e9eb68SKevin Wolf 
6293e9eb68SKevin Wolf enum {
635f8ae8e2SHervé Poussineau     BUSY    = 0x10,
645f8ae8e2SHervé Poussineau     NONDMA  = 0x20,
6593e9eb68SKevin Wolf     RQM     = 0x80,
6693e9eb68SKevin Wolf     DIO     = 0x40,
6793e9eb68SKevin Wolf 
6893e9eb68SKevin Wolf     DSKCHG  = 0x80,
6993e9eb68SKevin Wolf };
7093e9eb68SKevin Wolf 
71748bfb4eSStefan Weil static char test_image[] = "/tmp/qtest.XXXXXX";
7293e9eb68SKevin Wolf 
7393e9eb68SKevin Wolf #define assert_bit_set(data, mask) g_assert_cmphex((data) & (mask), ==, (mask))
7493e9eb68SKevin Wolf #define assert_bit_clear(data, mask) g_assert_cmphex((data) & (mask), ==, 0)
7593e9eb68SKevin Wolf 
767cd33161SPavel Hrdina static uint8_t base = 0x70;
777cd33161SPavel Hrdina 
787cd33161SPavel Hrdina enum {
797cd33161SPavel Hrdina     CMOS_FLOPPY     = 0x10,
807cd33161SPavel Hrdina };
817cd33161SPavel Hrdina 
8293e9eb68SKevin Wolf static void floppy_send(uint8_t byte)
8393e9eb68SKevin Wolf {
8493e9eb68SKevin Wolf     uint8_t msr;
8593e9eb68SKevin Wolf 
8693e9eb68SKevin Wolf     msr = inb(FLOPPY_BASE + reg_msr);
8793e9eb68SKevin Wolf     assert_bit_set(msr, RQM);
8893e9eb68SKevin Wolf     assert_bit_clear(msr, DIO);
8993e9eb68SKevin Wolf 
9093e9eb68SKevin Wolf     outb(FLOPPY_BASE + reg_fifo, byte);
9193e9eb68SKevin Wolf }
9293e9eb68SKevin Wolf 
9393e9eb68SKevin Wolf static uint8_t floppy_recv(void)
9493e9eb68SKevin Wolf {
9593e9eb68SKevin Wolf     uint8_t msr;
9693e9eb68SKevin Wolf 
9793e9eb68SKevin Wolf     msr = inb(FLOPPY_BASE + reg_msr);
9893e9eb68SKevin Wolf     assert_bit_set(msr, RQM | DIO);
9993e9eb68SKevin Wolf 
10093e9eb68SKevin Wolf     return inb(FLOPPY_BASE + reg_fifo);
10193e9eb68SKevin Wolf }
10293e9eb68SKevin Wolf 
103c3cdc1b0SKevin Wolf /* pcn: Present Cylinder Number */
104c3cdc1b0SKevin Wolf static void ack_irq(uint8_t *pcn)
10593e9eb68SKevin Wolf {
10698272dbbSPavel Hrdina     uint8_t ret;
10798272dbbSPavel Hrdina 
10893e9eb68SKevin Wolf     g_assert(get_irq(FLOPPY_IRQ));
10993e9eb68SKevin Wolf     floppy_send(CMD_SENSE_INT);
11093e9eb68SKevin Wolf     floppy_recv();
11198272dbbSPavel Hrdina 
112c3cdc1b0SKevin Wolf     ret = floppy_recv();
113c3cdc1b0SKevin Wolf     if (pcn != NULL) {
114c3cdc1b0SKevin Wolf         *pcn = ret;
115c3cdc1b0SKevin Wolf     }
116c3cdc1b0SKevin Wolf 
117c3cdc1b0SKevin Wolf     g_assert(!get_irq(FLOPPY_IRQ));
11893e9eb68SKevin Wolf }
11993e9eb68SKevin Wolf 
1206f442fe8SHervé Poussineau static uint8_t send_read_command(uint8_t cmd)
1218b9ef60dSPavel Hrdina {
1228b9ef60dSPavel Hrdina     uint8_t drive = 0;
1238b9ef60dSPavel Hrdina     uint8_t head = 0;
1248b9ef60dSPavel Hrdina     uint8_t cyl = 0;
1258b9ef60dSPavel Hrdina     uint8_t sect_addr = 1;
1268b9ef60dSPavel Hrdina     uint8_t sect_size = 2;
1278b9ef60dSPavel Hrdina     uint8_t eot = 1;
1288b9ef60dSPavel Hrdina     uint8_t gap = 0x1b;
1298b9ef60dSPavel Hrdina     uint8_t gpl = 0xff;
1308b9ef60dSPavel Hrdina 
1318b9ef60dSPavel Hrdina     uint8_t msr = 0;
1328b9ef60dSPavel Hrdina     uint8_t st0;
1338b9ef60dSPavel Hrdina 
1348b9ef60dSPavel Hrdina     uint8_t ret = 0;
1358b9ef60dSPavel Hrdina 
1366f442fe8SHervé Poussineau     floppy_send(cmd);
1378b9ef60dSPavel Hrdina     floppy_send(head << 2 | drive);
1388b9ef60dSPavel Hrdina     g_assert(!get_irq(FLOPPY_IRQ));
1398b9ef60dSPavel Hrdina     floppy_send(cyl);
1408b9ef60dSPavel Hrdina     floppy_send(head);
1418b9ef60dSPavel Hrdina     floppy_send(sect_addr);
1428b9ef60dSPavel Hrdina     floppy_send(sect_size);
1438b9ef60dSPavel Hrdina     floppy_send(eot);
1448b9ef60dSPavel Hrdina     floppy_send(gap);
1458b9ef60dSPavel Hrdina     floppy_send(gpl);
1468b9ef60dSPavel Hrdina 
1478b9ef60dSPavel Hrdina     uint8_t i = 0;
1488b9ef60dSPavel Hrdina     uint8_t n = 2;
1498b9ef60dSPavel Hrdina     for (; i < n; i++) {
1508b9ef60dSPavel Hrdina         msr = inb(FLOPPY_BASE + reg_msr);
1518b9ef60dSPavel Hrdina         if (msr == 0xd0) {
1528b9ef60dSPavel Hrdina             break;
1538b9ef60dSPavel Hrdina         }
1548b9ef60dSPavel Hrdina         sleep(1);
1558b9ef60dSPavel Hrdina     }
1568b9ef60dSPavel Hrdina 
1578b9ef60dSPavel Hrdina     if (i >= n) {
1588b9ef60dSPavel Hrdina         return 1;
1598b9ef60dSPavel Hrdina     }
1608b9ef60dSPavel Hrdina 
1618b9ef60dSPavel Hrdina     st0 = floppy_recv();
162075f5532SHervé Poussineau     if (st0 != 0x40) {
1638b9ef60dSPavel Hrdina         ret = 1;
1648b9ef60dSPavel Hrdina     }
1658b9ef60dSPavel Hrdina 
1668b9ef60dSPavel Hrdina     floppy_recv();
1678b9ef60dSPavel Hrdina     floppy_recv();
1688b9ef60dSPavel Hrdina     floppy_recv();
1698b9ef60dSPavel Hrdina     floppy_recv();
1708b9ef60dSPavel Hrdina     floppy_recv();
1718b9ef60dSPavel Hrdina     floppy_recv();
1728b9ef60dSPavel Hrdina 
1738b9ef60dSPavel Hrdina     return ret;
1748b9ef60dSPavel Hrdina }
1758b9ef60dSPavel Hrdina 
1765f8ae8e2SHervé Poussineau static uint8_t send_read_no_dma_command(int nb_sect, uint8_t expected_st0)
1775f8ae8e2SHervé Poussineau {
1785f8ae8e2SHervé Poussineau     uint8_t drive = 0;
1795f8ae8e2SHervé Poussineau     uint8_t head = 0;
1805f8ae8e2SHervé Poussineau     uint8_t cyl = 0;
1815f8ae8e2SHervé Poussineau     uint8_t sect_addr = 1;
1825f8ae8e2SHervé Poussineau     uint8_t sect_size = 2;
1835f8ae8e2SHervé Poussineau     uint8_t eot = nb_sect;
1845f8ae8e2SHervé Poussineau     uint8_t gap = 0x1b;
1855f8ae8e2SHervé Poussineau     uint8_t gpl = 0xff;
1865f8ae8e2SHervé Poussineau 
1875f8ae8e2SHervé Poussineau     uint8_t msr = 0;
1885f8ae8e2SHervé Poussineau     uint8_t st0;
1895f8ae8e2SHervé Poussineau 
1905f8ae8e2SHervé Poussineau     uint8_t ret = 0;
1915f8ae8e2SHervé Poussineau 
1925f8ae8e2SHervé Poussineau     floppy_send(CMD_READ);
1935f8ae8e2SHervé Poussineau     floppy_send(head << 2 | drive);
1945f8ae8e2SHervé Poussineau     g_assert(!get_irq(FLOPPY_IRQ));
1955f8ae8e2SHervé Poussineau     floppy_send(cyl);
1965f8ae8e2SHervé Poussineau     floppy_send(head);
1975f8ae8e2SHervé Poussineau     floppy_send(sect_addr);
1985f8ae8e2SHervé Poussineau     floppy_send(sect_size);
1995f8ae8e2SHervé Poussineau     floppy_send(eot);
2005f8ae8e2SHervé Poussineau     floppy_send(gap);
2015f8ae8e2SHervé Poussineau     floppy_send(gpl);
2025f8ae8e2SHervé Poussineau 
2035f8ae8e2SHervé Poussineau     uint16_t i = 0;
2045f8ae8e2SHervé Poussineau     uint8_t n = 2;
2055f8ae8e2SHervé Poussineau     for (; i < n; i++) {
2065f8ae8e2SHervé Poussineau         msr = inb(FLOPPY_BASE + reg_msr);
2075f8ae8e2SHervé Poussineau         if (msr == (BUSY | NONDMA | DIO | RQM)) {
2085f8ae8e2SHervé Poussineau             break;
2095f8ae8e2SHervé Poussineau         }
2105f8ae8e2SHervé Poussineau         sleep(1);
2115f8ae8e2SHervé Poussineau     }
2125f8ae8e2SHervé Poussineau 
2135f8ae8e2SHervé Poussineau     if (i >= n) {
2145f8ae8e2SHervé Poussineau         return 1;
2155f8ae8e2SHervé Poussineau     }
2165f8ae8e2SHervé Poussineau 
2175f8ae8e2SHervé Poussineau     /* Non-DMA mode */
2185f8ae8e2SHervé Poussineau     for (i = 0; i < 512 * 2 * nb_sect; i++) {
2195f8ae8e2SHervé Poussineau         msr = inb(FLOPPY_BASE + reg_msr);
2205f8ae8e2SHervé Poussineau         assert_bit_set(msr, BUSY | RQM | DIO);
2215f8ae8e2SHervé Poussineau         inb(FLOPPY_BASE + reg_fifo);
2225f8ae8e2SHervé Poussineau     }
2235f8ae8e2SHervé Poussineau 
2244964e18eSKevin Wolf     msr = inb(FLOPPY_BASE + reg_msr);
2254964e18eSKevin Wolf     assert_bit_set(msr, BUSY | RQM | DIO);
2264964e18eSKevin Wolf     g_assert(get_irq(FLOPPY_IRQ));
2274964e18eSKevin Wolf 
2285f8ae8e2SHervé Poussineau     st0 = floppy_recv();
2295f8ae8e2SHervé Poussineau     if (st0 != expected_st0) {
2305f8ae8e2SHervé Poussineau         ret = 1;
2315f8ae8e2SHervé Poussineau     }
2325f8ae8e2SHervé Poussineau 
2335f8ae8e2SHervé Poussineau     floppy_recv();
2345f8ae8e2SHervé Poussineau     floppy_recv();
2355f8ae8e2SHervé Poussineau     floppy_recv();
2365f8ae8e2SHervé Poussineau     floppy_recv();
2375f8ae8e2SHervé Poussineau     floppy_recv();
2384964e18eSKevin Wolf     g_assert(get_irq(FLOPPY_IRQ));
2395f8ae8e2SHervé Poussineau     floppy_recv();
2405f8ae8e2SHervé Poussineau 
2414964e18eSKevin Wolf     /* Check that we're back in command phase */
2424964e18eSKevin Wolf     msr = inb(FLOPPY_BASE + reg_msr);
2434964e18eSKevin Wolf     assert_bit_clear(msr, BUSY | DIO);
2444964e18eSKevin Wolf     assert_bit_set(msr, RQM);
2454964e18eSKevin Wolf     g_assert(!get_irq(FLOPPY_IRQ));
2464964e18eSKevin Wolf 
2475f8ae8e2SHervé Poussineau     return ret;
2485f8ae8e2SHervé Poussineau }
2495f8ae8e2SHervé Poussineau 
250c3cdc1b0SKevin Wolf static void send_seek(int cyl)
25193e9eb68SKevin Wolf {
25293e9eb68SKevin Wolf     int drive = 0;
25393e9eb68SKevin Wolf     int head = 0;
25493e9eb68SKevin Wolf 
25593e9eb68SKevin Wolf     floppy_send(CMD_SEEK);
25693e9eb68SKevin Wolf     floppy_send(head << 2 | drive);
25793e9eb68SKevin Wolf     g_assert(!get_irq(FLOPPY_IRQ));
25893e9eb68SKevin Wolf     floppy_send(cyl);
259c3cdc1b0SKevin Wolf     ack_irq(NULL);
26093e9eb68SKevin Wolf }
26193e9eb68SKevin Wolf 
2627cd33161SPavel Hrdina static uint8_t cmos_read(uint8_t reg)
2637cd33161SPavel Hrdina {
2647cd33161SPavel Hrdina     outb(base + 0, reg);
2657cd33161SPavel Hrdina     return inb(base + 1);
2667cd33161SPavel Hrdina }
2677cd33161SPavel Hrdina 
2687cd33161SPavel Hrdina static void test_cmos(void)
2697cd33161SPavel Hrdina {
2707cd33161SPavel Hrdina     uint8_t cmos;
2717cd33161SPavel Hrdina 
2727cd33161SPavel Hrdina     cmos = cmos_read(CMOS_FLOPPY);
27362c76250SJohn Snow     g_assert(cmos == 0x40 || cmos == 0x50);
2747cd33161SPavel Hrdina }
2757cd33161SPavel Hrdina 
2767cd33161SPavel Hrdina static void test_no_media_on_start(void)
2777cd33161SPavel Hrdina {
2787cd33161SPavel Hrdina     uint8_t dir;
2797cd33161SPavel Hrdina 
2807cd33161SPavel Hrdina     /* Media changed bit must be set all time after start if there is
2817cd33161SPavel Hrdina      * no media in drive. */
2827cd33161SPavel Hrdina     dir = inb(FLOPPY_BASE + reg_dir);
2837cd33161SPavel Hrdina     assert_bit_set(dir, DSKCHG);
2847cd33161SPavel Hrdina     dir = inb(FLOPPY_BASE + reg_dir);
2857cd33161SPavel Hrdina     assert_bit_set(dir, DSKCHG);
286c3cdc1b0SKevin Wolf     send_seek(1);
2877cd33161SPavel Hrdina     dir = inb(FLOPPY_BASE + reg_dir);
2887cd33161SPavel Hrdina     assert_bit_set(dir, DSKCHG);
2897cd33161SPavel Hrdina     dir = inb(FLOPPY_BASE + reg_dir);
2907cd33161SPavel Hrdina     assert_bit_set(dir, DSKCHG);
2917cd33161SPavel Hrdina }
2927cd33161SPavel Hrdina 
2938b9ef60dSPavel Hrdina static void test_read_without_media(void)
2948b9ef60dSPavel Hrdina {
2958b9ef60dSPavel Hrdina     uint8_t ret;
2968b9ef60dSPavel Hrdina 
2976f442fe8SHervé Poussineau     ret = send_read_command(CMD_READ);
2988b9ef60dSPavel Hrdina     g_assert(ret == 0);
2998b9ef60dSPavel Hrdina }
3008b9ef60dSPavel Hrdina 
3011f507913SHervé Poussineau static void test_media_insert(void)
30293e9eb68SKevin Wolf {
30393e9eb68SKevin Wolf     uint8_t dir;
30493e9eb68SKevin Wolf 
3057cd33161SPavel Hrdina     /* Insert media in drive. DSKCHK should not be reset until a step pulse
3067cd33161SPavel Hrdina      * is sent. */
307ccb61bddSEric Blake     qmp_discard_response("{'execute':'blockdev-change-medium', 'arguments':{"
308ccb61bddSEric Blake                          " 'id':'floppy0', 'filename': %s, 'format': 'raw' }}",
3090d1aa05eSStefan Hajnoczi                          test_image);
3107cd33161SPavel Hrdina 
3117cd33161SPavel Hrdina     dir = inb(FLOPPY_BASE + reg_dir);
3127cd33161SPavel Hrdina     assert_bit_set(dir, DSKCHG);
3137cd33161SPavel Hrdina     dir = inb(FLOPPY_BASE + reg_dir);
3147cd33161SPavel Hrdina     assert_bit_set(dir, DSKCHG);
3157cd33161SPavel Hrdina 
316c3cdc1b0SKevin Wolf     send_seek(0);
31759240c34SPavel Hrdina     dir = inb(FLOPPY_BASE + reg_dir);
31859240c34SPavel Hrdina     assert_bit_set(dir, DSKCHG);
31959240c34SPavel Hrdina     dir = inb(FLOPPY_BASE + reg_dir);
32059240c34SPavel Hrdina     assert_bit_set(dir, DSKCHG);
32159240c34SPavel Hrdina 
32259240c34SPavel Hrdina     /* Step to next track should clear DSKCHG bit. */
323c3cdc1b0SKevin Wolf     send_seek(1);
3247cd33161SPavel Hrdina     dir = inb(FLOPPY_BASE + reg_dir);
3257cd33161SPavel Hrdina     assert_bit_clear(dir, DSKCHG);
32693e9eb68SKevin Wolf     dir = inb(FLOPPY_BASE + reg_dir);
32793e9eb68SKevin Wolf     assert_bit_clear(dir, DSKCHG);
3281f507913SHervé Poussineau }
3291f507913SHervé Poussineau 
3301f507913SHervé Poussineau static void test_media_change(void)
3311f507913SHervé Poussineau {
3321f507913SHervé Poussineau     uint8_t dir;
3331f507913SHervé Poussineau 
3341f507913SHervé Poussineau     test_media_insert();
33593e9eb68SKevin Wolf 
33693e9eb68SKevin Wolf     /* Eject the floppy and check that DSKCHG is set. Reading it out doesn't
33793e9eb68SKevin Wolf      * reset the bit. */
3380d1aa05eSStefan Hajnoczi     qmp_discard_response("{'execute':'eject', 'arguments':{"
339ccb61bddSEric Blake                          " 'id':'floppy0' }}");
34093e9eb68SKevin Wolf 
34193e9eb68SKevin Wolf     dir = inb(FLOPPY_BASE + reg_dir);
34293e9eb68SKevin Wolf     assert_bit_set(dir, DSKCHG);
34393e9eb68SKevin Wolf     dir = inb(FLOPPY_BASE + reg_dir);
34493e9eb68SKevin Wolf     assert_bit_set(dir, DSKCHG);
34593e9eb68SKevin Wolf 
346c3cdc1b0SKevin Wolf     send_seek(0);
34759240c34SPavel Hrdina     dir = inb(FLOPPY_BASE + reg_dir);
34859240c34SPavel Hrdina     assert_bit_set(dir, DSKCHG);
34959240c34SPavel Hrdina     dir = inb(FLOPPY_BASE + reg_dir);
35059240c34SPavel Hrdina     assert_bit_set(dir, DSKCHG);
35159240c34SPavel Hrdina 
352c3cdc1b0SKevin Wolf     send_seek(1);
35393e9eb68SKevin Wolf     dir = inb(FLOPPY_BASE + reg_dir);
35493e9eb68SKevin Wolf     assert_bit_set(dir, DSKCHG);
35593e9eb68SKevin Wolf     dir = inb(FLOPPY_BASE + reg_dir);
35693e9eb68SKevin Wolf     assert_bit_set(dir, DSKCHG);
35793e9eb68SKevin Wolf }
35893e9eb68SKevin Wolf 
359b3ce604eSPavel Hrdina static void test_sense_interrupt(void)
360b3ce604eSPavel Hrdina {
361b3ce604eSPavel Hrdina     int drive = 0;
362b3ce604eSPavel Hrdina     int head = 0;
363b3ce604eSPavel Hrdina     int cyl = 0;
364b3ce604eSPavel Hrdina     int ret = 0;
365b3ce604eSPavel Hrdina 
366b3ce604eSPavel Hrdina     floppy_send(CMD_SENSE_INT);
367b3ce604eSPavel Hrdina     ret = floppy_recv();
368b3ce604eSPavel Hrdina     g_assert(ret == 0x80);
369b3ce604eSPavel Hrdina 
370b3ce604eSPavel Hrdina     floppy_send(CMD_SEEK);
371b3ce604eSPavel Hrdina     floppy_send(head << 2 | drive);
372b3ce604eSPavel Hrdina     g_assert(!get_irq(FLOPPY_IRQ));
373b3ce604eSPavel Hrdina     floppy_send(cyl);
374b3ce604eSPavel Hrdina 
375b3ce604eSPavel Hrdina     floppy_send(CMD_SENSE_INT);
376b3ce604eSPavel Hrdina     ret = floppy_recv();
377b3ce604eSPavel Hrdina     g_assert(ret == 0x20);
378b3ce604eSPavel Hrdina     floppy_recv();
379b3ce604eSPavel Hrdina }
380b3ce604eSPavel Hrdina 
38198272dbbSPavel Hrdina static void test_relative_seek(void)
38298272dbbSPavel Hrdina {
38398272dbbSPavel Hrdina     uint8_t drive = 0;
38498272dbbSPavel Hrdina     uint8_t head = 0;
38598272dbbSPavel Hrdina     uint8_t cyl = 1;
386c3cdc1b0SKevin Wolf     uint8_t pcn;
38798272dbbSPavel Hrdina 
38898272dbbSPavel Hrdina     /* Send seek to track 0 */
389c3cdc1b0SKevin Wolf     send_seek(0);
39098272dbbSPavel Hrdina 
39198272dbbSPavel Hrdina     /* Send relative seek to increase track by 1 */
39298272dbbSPavel Hrdina     floppy_send(CMD_RELATIVE_SEEK_IN);
39398272dbbSPavel Hrdina     floppy_send(head << 2 | drive);
39498272dbbSPavel Hrdina     g_assert(!get_irq(FLOPPY_IRQ));
39598272dbbSPavel Hrdina     floppy_send(cyl);
39698272dbbSPavel Hrdina 
397c3cdc1b0SKevin Wolf     ack_irq(&pcn);
398c3cdc1b0SKevin Wolf     g_assert(pcn == 1);
39998272dbbSPavel Hrdina 
40098272dbbSPavel Hrdina     /* Send relative seek to decrease track by 1 */
40198272dbbSPavel Hrdina     floppy_send(CMD_RELATIVE_SEEK_OUT);
40298272dbbSPavel Hrdina     floppy_send(head << 2 | drive);
40398272dbbSPavel Hrdina     g_assert(!get_irq(FLOPPY_IRQ));
40498272dbbSPavel Hrdina     floppy_send(cyl);
40598272dbbSPavel Hrdina 
406c3cdc1b0SKevin Wolf     ack_irq(&pcn);
407c3cdc1b0SKevin Wolf     g_assert(pcn == 0);
40898272dbbSPavel Hrdina }
40998272dbbSPavel Hrdina 
41067f194bdSKevin Wolf static void test_read_id(void)
41167f194bdSKevin Wolf {
41267f194bdSKevin Wolf     uint8_t drive = 0;
41367f194bdSKevin Wolf     uint8_t head = 0;
41467f194bdSKevin Wolf     uint8_t cyl;
41567f194bdSKevin Wolf     uint8_t st0;
4164964e18eSKevin Wolf     uint8_t msr;
41767f194bdSKevin Wolf 
41867f194bdSKevin Wolf     /* Seek to track 0 and check with READ ID */
41967f194bdSKevin Wolf     send_seek(0);
42067f194bdSKevin Wolf 
42167f194bdSKevin Wolf     floppy_send(CMD_READ_ID);
42267f194bdSKevin Wolf     g_assert(!get_irq(FLOPPY_IRQ));
42367f194bdSKevin Wolf     floppy_send(head << 2 | drive);
42467f194bdSKevin Wolf 
4254964e18eSKevin Wolf     msr = inb(FLOPPY_BASE + reg_msr);
4264964e18eSKevin Wolf     if (!get_irq(FLOPPY_IRQ)) {
4274964e18eSKevin Wolf         assert_bit_set(msr, BUSY);
4284964e18eSKevin Wolf         assert_bit_clear(msr, RQM);
4294964e18eSKevin Wolf     }
4304964e18eSKevin Wolf 
43167f194bdSKevin Wolf     while (!get_irq(FLOPPY_IRQ)) {
43267f194bdSKevin Wolf         /* qemu involves a timer with READ ID... */
43367f194bdSKevin Wolf         clock_step(1000000000LL / 50);
43467f194bdSKevin Wolf     }
43567f194bdSKevin Wolf 
4364964e18eSKevin Wolf     msr = inb(FLOPPY_BASE + reg_msr);
4374964e18eSKevin Wolf     assert_bit_set(msr, BUSY | RQM | DIO);
4384964e18eSKevin Wolf 
43967f194bdSKevin Wolf     st0 = floppy_recv();
44067f194bdSKevin Wolf     floppy_recv();
44167f194bdSKevin Wolf     floppy_recv();
44267f194bdSKevin Wolf     cyl = floppy_recv();
44367f194bdSKevin Wolf     head = floppy_recv();
44467f194bdSKevin Wolf     floppy_recv();
4454964e18eSKevin Wolf     g_assert(get_irq(FLOPPY_IRQ));
44667f194bdSKevin Wolf     floppy_recv();
4474964e18eSKevin Wolf     g_assert(!get_irq(FLOPPY_IRQ));
44867f194bdSKevin Wolf 
44967f194bdSKevin Wolf     g_assert_cmpint(cyl, ==, 0);
45067f194bdSKevin Wolf     g_assert_cmpint(head, ==, 0);
45167f194bdSKevin Wolf     g_assert_cmpint(st0, ==, head << 2);
45267f194bdSKevin Wolf 
45367f194bdSKevin Wolf     /* Seek to track 8 on head 1 and check with READ ID */
45467f194bdSKevin Wolf     head = 1;
45567f194bdSKevin Wolf     cyl = 8;
45667f194bdSKevin Wolf 
45767f194bdSKevin Wolf     floppy_send(CMD_SEEK);
45867f194bdSKevin Wolf     floppy_send(head << 2 | drive);
45967f194bdSKevin Wolf     g_assert(!get_irq(FLOPPY_IRQ));
46067f194bdSKevin Wolf     floppy_send(cyl);
46167f194bdSKevin Wolf     g_assert(get_irq(FLOPPY_IRQ));
46267f194bdSKevin Wolf     ack_irq(NULL);
46367f194bdSKevin Wolf 
46467f194bdSKevin Wolf     floppy_send(CMD_READ_ID);
46567f194bdSKevin Wolf     g_assert(!get_irq(FLOPPY_IRQ));
46667f194bdSKevin Wolf     floppy_send(head << 2 | drive);
46767f194bdSKevin Wolf 
4684964e18eSKevin Wolf     msr = inb(FLOPPY_BASE + reg_msr);
4694964e18eSKevin Wolf     if (!get_irq(FLOPPY_IRQ)) {
4704964e18eSKevin Wolf         assert_bit_set(msr, BUSY);
4714964e18eSKevin Wolf         assert_bit_clear(msr, RQM);
4724964e18eSKevin Wolf     }
4734964e18eSKevin Wolf 
47467f194bdSKevin Wolf     while (!get_irq(FLOPPY_IRQ)) {
47567f194bdSKevin Wolf         /* qemu involves a timer with READ ID... */
47667f194bdSKevin Wolf         clock_step(1000000000LL / 50);
47767f194bdSKevin Wolf     }
47867f194bdSKevin Wolf 
4794964e18eSKevin Wolf     msr = inb(FLOPPY_BASE + reg_msr);
4804964e18eSKevin Wolf     assert_bit_set(msr, BUSY | RQM | DIO);
4814964e18eSKevin Wolf 
48267f194bdSKevin Wolf     st0 = floppy_recv();
48367f194bdSKevin Wolf     floppy_recv();
48467f194bdSKevin Wolf     floppy_recv();
48567f194bdSKevin Wolf     cyl = floppy_recv();
48667f194bdSKevin Wolf     head = floppy_recv();
48767f194bdSKevin Wolf     floppy_recv();
4884964e18eSKevin Wolf     g_assert(get_irq(FLOPPY_IRQ));
48967f194bdSKevin Wolf     floppy_recv();
4904964e18eSKevin Wolf     g_assert(!get_irq(FLOPPY_IRQ));
49167f194bdSKevin Wolf 
49267f194bdSKevin Wolf     g_assert_cmpint(cyl, ==, 8);
49367f194bdSKevin Wolf     g_assert_cmpint(head, ==, 1);
49467f194bdSKevin Wolf     g_assert_cmpint(st0, ==, head << 2);
49567f194bdSKevin Wolf }
49667f194bdSKevin Wolf 
4975f8ae8e2SHervé Poussineau static void test_read_no_dma_1(void)
4985f8ae8e2SHervé Poussineau {
4995f8ae8e2SHervé Poussineau     uint8_t ret;
5005f8ae8e2SHervé Poussineau 
5015f8ae8e2SHervé Poussineau     outb(FLOPPY_BASE + reg_dor, inb(FLOPPY_BASE + reg_dor) & ~0x08);
5025f8ae8e2SHervé Poussineau     send_seek(0);
503075f5532SHervé Poussineau     ret = send_read_no_dma_command(1, 0x04);
5045f8ae8e2SHervé Poussineau     g_assert(ret == 0);
5055f8ae8e2SHervé Poussineau }
5065f8ae8e2SHervé Poussineau 
5075f8ae8e2SHervé Poussineau static void test_read_no_dma_18(void)
5085f8ae8e2SHervé Poussineau {
5095f8ae8e2SHervé Poussineau     uint8_t ret;
5105f8ae8e2SHervé Poussineau 
5115f8ae8e2SHervé Poussineau     outb(FLOPPY_BASE + reg_dor, inb(FLOPPY_BASE + reg_dor) & ~0x08);
5125f8ae8e2SHervé Poussineau     send_seek(0);
513075f5532SHervé Poussineau     ret = send_read_no_dma_command(18, 0x04);
5145f8ae8e2SHervé Poussineau     g_assert(ret == 0);
5155f8ae8e2SHervé Poussineau }
5165f8ae8e2SHervé Poussineau 
5175f8ae8e2SHervé Poussineau static void test_read_no_dma_19(void)
5185f8ae8e2SHervé Poussineau {
5195f8ae8e2SHervé Poussineau     uint8_t ret;
5205f8ae8e2SHervé Poussineau 
5215f8ae8e2SHervé Poussineau     outb(FLOPPY_BASE + reg_dor, inb(FLOPPY_BASE + reg_dor) & ~0x08);
5225f8ae8e2SHervé Poussineau     send_seek(0);
5235f8ae8e2SHervé Poussineau     ret = send_read_no_dma_command(19, 0x20);
5245f8ae8e2SHervé Poussineau     g_assert(ret == 0);
5255f8ae8e2SHervé Poussineau }
5265f8ae8e2SHervé Poussineau 
5276f442fe8SHervé Poussineau static void test_verify(void)
5286f442fe8SHervé Poussineau {
5296f442fe8SHervé Poussineau     uint8_t ret;
5306f442fe8SHervé Poussineau 
5316f442fe8SHervé Poussineau     ret = send_read_command(CMD_VERIFY);
5326f442fe8SHervé Poussineau     g_assert(ret == 0);
5336f442fe8SHervé Poussineau }
5346f442fe8SHervé Poussineau 
5353359847eSBlue Swirl /* success if no crash or abort */
5363359847eSBlue Swirl static void fuzz_registers(void)
5373359847eSBlue Swirl {
5383359847eSBlue Swirl     unsigned int i;
5393359847eSBlue Swirl 
5403359847eSBlue Swirl     for (i = 0; i < 1000; i++) {
5413359847eSBlue Swirl         uint8_t reg, val;
5423359847eSBlue Swirl 
5433359847eSBlue Swirl         reg = (uint8_t)g_test_rand_int_range(0, 8);
5443359847eSBlue Swirl         val = (uint8_t)g_test_rand_int_range(0, 256);
5453359847eSBlue Swirl 
5463359847eSBlue Swirl         outb(FLOPPY_BASE + reg, val);
5473359847eSBlue Swirl         inb(FLOPPY_BASE + reg);
5483359847eSBlue Swirl     }
5493359847eSBlue Swirl }
5503359847eSBlue Swirl 
551cc20926eSPhilippe Mathieu-Daudé static bool qtest_check_clang_sanitizer(void)
552cc20926eSPhilippe Mathieu-Daudé {
553*638466f7SMarc-André Lureau #ifdef QEMU_SANITIZE_ADDRESS
554cc20926eSPhilippe Mathieu-Daudé     return true;
555cc20926eSPhilippe Mathieu-Daudé #else
556cc20926eSPhilippe Mathieu-Daudé     g_test_skip("QEMU not configured using --enable-sanitizers");
557cc20926eSPhilippe Mathieu-Daudé     return false;
558cc20926eSPhilippe Mathieu-Daudé #endif
559cc20926eSPhilippe Mathieu-Daudé }
560cc20926eSPhilippe Mathieu-Daudé static void test_cve_2021_20196(void)
561cc20926eSPhilippe Mathieu-Daudé {
562cc20926eSPhilippe Mathieu-Daudé     QTestState *s;
563cc20926eSPhilippe Mathieu-Daudé 
564cc20926eSPhilippe Mathieu-Daudé     if (!qtest_check_clang_sanitizer()) {
565cc20926eSPhilippe Mathieu-Daudé         return;
566cc20926eSPhilippe Mathieu-Daudé     }
567cc20926eSPhilippe Mathieu-Daudé 
568cc20926eSPhilippe Mathieu-Daudé     s = qtest_initf("-nographic -m 32M -nodefaults " DRIVE_FLOPPY_BLANK);
569cc20926eSPhilippe Mathieu-Daudé 
570cc20926eSPhilippe Mathieu-Daudé     qtest_outw(s, 0x3f4, 0x0500);
571cc20926eSPhilippe Mathieu-Daudé     qtest_outb(s, 0x3f5, 0x00);
572cc20926eSPhilippe Mathieu-Daudé     qtest_outb(s, 0x3f5, 0x00);
573cc20926eSPhilippe Mathieu-Daudé     qtest_outw(s, 0x3f4, 0x0000);
574cc20926eSPhilippe Mathieu-Daudé     qtest_outb(s, 0x3f5, 0x00);
575cc20926eSPhilippe Mathieu-Daudé     qtest_outw(s, 0x3f1, 0x0400);
576cc20926eSPhilippe Mathieu-Daudé     qtest_outw(s, 0x3f4, 0x0000);
577cc20926eSPhilippe Mathieu-Daudé     qtest_outw(s, 0x3f4, 0x0000);
578cc20926eSPhilippe Mathieu-Daudé     qtest_outb(s, 0x3f5, 0x00);
579cc20926eSPhilippe Mathieu-Daudé     qtest_outb(s, 0x3f5, 0x01);
580cc20926eSPhilippe Mathieu-Daudé     qtest_outw(s, 0x3f1, 0x0500);
581cc20926eSPhilippe Mathieu-Daudé     qtest_outb(s, 0x3f5, 0x00);
582cc20926eSPhilippe Mathieu-Daudé     qtest_quit(s);
583cc20926eSPhilippe Mathieu-Daudé }
584cc20926eSPhilippe Mathieu-Daudé 
58593e9eb68SKevin Wolf int main(int argc, char **argv)
58693e9eb68SKevin Wolf {
58793e9eb68SKevin Wolf     int fd;
58893e9eb68SKevin Wolf     int ret;
58993e9eb68SKevin Wolf 
59093e9eb68SKevin Wolf     /* Create a temporary raw image */
59193e9eb68SKevin Wolf     fd = mkstemp(test_image);
59293e9eb68SKevin Wolf     g_assert(fd >= 0);
59393e9eb68SKevin Wolf     ret = ftruncate(fd, TEST_IMAGE_SIZE);
59493e9eb68SKevin Wolf     g_assert(ret == 0);
59593e9eb68SKevin Wolf     close(fd);
59693e9eb68SKevin Wolf 
59793e9eb68SKevin Wolf     /* Run the tests */
59893e9eb68SKevin Wolf     g_test_init(&argc, &argv, NULL);
59993e9eb68SKevin Wolf 
600fedcc379SDr. David Alan Gilbert     qtest_start("-machine pc -device floppy,id=floppy0");
60193e9eb68SKevin Wolf     qtest_irq_intercept_in(global_qtest, "ioapic");
6027cd33161SPavel Hrdina     qtest_add_func("/fdc/cmos", test_cmos);
6037cd33161SPavel Hrdina     qtest_add_func("/fdc/no_media_on_start", test_no_media_on_start);
6048b9ef60dSPavel Hrdina     qtest_add_func("/fdc/read_without_media", test_read_without_media);
60593e9eb68SKevin Wolf     qtest_add_func("/fdc/media_change", test_media_change);
606b3ce604eSPavel Hrdina     qtest_add_func("/fdc/sense_interrupt", test_sense_interrupt);
60798272dbbSPavel Hrdina     qtest_add_func("/fdc/relative_seek", test_relative_seek);
60867f194bdSKevin Wolf     qtest_add_func("/fdc/read_id", test_read_id);
6096f442fe8SHervé Poussineau     qtest_add_func("/fdc/verify", test_verify);
61044212dccSHervé Poussineau     qtest_add_func("/fdc/media_insert", test_media_insert);
6115f8ae8e2SHervé Poussineau     qtest_add_func("/fdc/read_no_dma_1", test_read_no_dma_1);
6125f8ae8e2SHervé Poussineau     qtest_add_func("/fdc/read_no_dma_18", test_read_no_dma_18);
6135f8ae8e2SHervé Poussineau     qtest_add_func("/fdc/read_no_dma_19", test_read_no_dma_19);
6143359847eSBlue Swirl     qtest_add_func("/fdc/fuzz-registers", fuzz_registers);
615cc20926eSPhilippe Mathieu-Daudé     qtest_add_func("/fdc/fuzz/cve_2021_20196", test_cve_2021_20196);
61693e9eb68SKevin Wolf 
61793e9eb68SKevin Wolf     ret = g_test_run();
61893e9eb68SKevin Wolf 
61993e9eb68SKevin Wolf     /* Cleanup */
6201d9358e6SMarkus Armbruster     qtest_end();
62193e9eb68SKevin Wolf     unlink(test_image);
62293e9eb68SKevin Wolf 
62393e9eb68SKevin Wolf     return ret;
62493e9eb68SKevin Wolf }
625