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