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