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