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