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