[PATCH 3/3] tests/qtest/fdc-test: check that READ ID answers for the drive it names
Christian Quante <[email protected]>
| Newsgroups | org.nongnu.qemu-devel |
|---|---|
| Message-ID | <[email protected]> |
Commit d00567f7fb ("hw/block/fdc: select the drive named by the READ ID
command") added the missing SET_CUR_DRV() to fdctrl_handle_readid(), but
nothing keeps it there: the suite runs a machine with a single floppy
drive, where answering for the wrong drive cannot be told from answering
for the right one. Removing the call again leaves every existing test
case green.
Add a test case on a machine with two drives. Drive 0 holds a medium and
is selected by a SEEK, the way a driver spinning up the motor would select
it; drive 1 is empty. READ ID is then issued for drive 1, and two
independent properties of the answer have to hold: fdctrl_stop_transfer()
reports the drive in ST0, and an empty drive terminates abnormally with
ST1.MA. Without the fix the controller answers for drive 0, which
terminates normally with ST0 = 0x00.
Test cases carry their own command line, so that a test can ask for
something other than the default single drive. Both command lines name
the pc machine, so the check added by the previous patch still covers
every test case.
Signed-off-by: Christian Quante <[email protected]>
---
tests/qtest/fdc-test.c | 81 ++++++++++++++++++++++++++++++++++--------
1 file changed, 66 insertions(+), 15 deletions(-)
diff --git a/tests/qtest/fdc-test.c b/tests/qtest/fdc-test.c
index f480f4ba09..33d1b83aeb 100644
--- a/tests/qtest/fdc-test.c
+++ b/tests/qtest/fdc-test.c
@@ -67,6 +67,7 @@ enum {
enum {
ST0_IC_MASK = 0xc0, /* interrupt code */
ST0_IC_ABNTERM = 0x40, /* abnormal termination */
+ ST0_DS_MASK = 0x03, /* drive the answer is about */
ST1_MA = 0x01, /* missing address mark */
};
@@ -545,6 +546,47 @@ static void test_read_id_no_media(void)
g_assert_cmpint(st1 & ST1_MA, ==, ST1_MA);
}
+/*
+ * READ ID carries the drive number in its command byte and has to latch it.
+ * A controller that fails to do so answers for whichever drive was selected
+ * last. Here drive 0 holds a medium and is selected by the SEEK; drive 1 is
+ * empty, so answering for the wrong drive terminates normally (ST0 = 0x00)
+ * where the command has to fail, and names drive 0 rather than drive 1.
+ */
+static void test_read_id_other_drive(void)
+{
+ uint8_t drive = 1;
+ uint8_t head = 0;
+ uint8_t st0, st1;
+
+ media_insert();
+
+ /* SEEK latches drive 0, the way a driver spinning up the motor would */
+ send_seek(0);
+
+ floppy_send(CMD_READ_ID);
+ g_assert(!get_irq(FLOPPY_IRQ));
+ floppy_send(head << 2 | drive);
+
+ while (!get_irq(FLOPPY_IRQ)) {
+ clock_step(1000000000LL / 50);
+ }
+
+ st0 = floppy_recv();
+ st1 = floppy_recv();
+ floppy_recv(); /* ST2 */
+ floppy_recv(); /* cylinder */
+ floppy_recv(); /* head */
+ floppy_recv(); /* sector */
+ g_assert(get_irq(FLOPPY_IRQ));
+ floppy_recv(); /* sector size */
+ g_assert(!get_irq(FLOPPY_IRQ));
+
+ g_assert_cmpint(st0 & ST0_DS_MASK, ==, drive);
+ g_assert_cmpint(st0 & ST0_IC_MASK, ==, ST0_IC_ABNTERM);
+ g_assert_cmpint(st1 & ST1_MA, ==, ST1_MA);
+}
+
static void test_read_no_dma_1(void)
{
uint8_t ret;
@@ -656,6 +698,11 @@ static void test_cve_2021_3507(void)
qtest_quit(s);
}
+#define MACHINE_ONE_DRIVE "-machine pc -device floppy,id=floppy0"
+#define MACHINE_TWO_DRIVES \
+ "-machine pc -device floppy,id=floppy0,unit=0" \
+ " -device floppy,id=floppy1,unit=1"
+
/*
* Each test gets its own QEMU instance, so that none of them depends on the
* state another one left behind. Without this, several tests only pass in
@@ -667,35 +714,39 @@ static void test_cve_2021_3507(void)
typedef struct {
const char *path;
void (*fn)(void);
+ const char *args;
} FDCTest;
static const FDCTest fdc_tests[] = {
- { "/fdc/cmos", test_cmos },
- { "/fdc/no_media_on_start", test_no_media_on_start },
- { "/fdc/read_without_media", test_read_without_media },
- { "/fdc/media_change", test_media_change },
- { "/fdc/sense_interrupt", test_sense_interrupt },
- { "/fdc/relative_seek", test_relative_seek },
- { "/fdc/read_id", test_read_id },
- { "/fdc/read_id_no_media", test_read_id_no_media },
- { "/fdc/verify", test_verify },
- { "/fdc/media_insert", test_media_insert },
- { "/fdc/read_no_dma_1", test_read_no_dma_1 },
- { "/fdc/read_no_dma_18", test_read_no_dma_18 },
- { "/fdc/read_no_dma_19", test_read_no_dma_19 },
- { "/fdc/fuzz-registers", fuzz_registers },
+ { "/fdc/cmos", test_cmos, MACHINE_ONE_DRIVE },
+ { "/fdc/no_media_on_start", test_no_media_on_start, MACHINE_ONE_DRIVE },
+ { "/fdc/read_without_media", test_read_without_media, MACHINE_ONE_DRIVE },
+ { "/fdc/media_change", test_media_change, MACHINE_ONE_DRIVE },
+ { "/fdc/sense_interrupt", test_sense_interrupt, MACHINE_ONE_DRIVE },
+ { "/fdc/relative_seek", test_relative_seek, MACHINE_ONE_DRIVE },
+ { "/fdc/read_id", test_read_id, MACHINE_ONE_DRIVE },
+ { "/fdc/read_id_no_media", test_read_id_no_media, MACHINE_ONE_DRIVE },
+ { "/fdc/read_id_other_drive", test_read_id_other_drive,
+ MACHINE_TWO_DRIVES },
+ { "/fdc/verify", test_verify, MACHINE_ONE_DRIVE },
+ { "/fdc/media_insert", test_media_insert, MACHINE_ONE_DRIVE },
+ { "/fdc/read_no_dma_1", test_read_no_dma_1, MACHINE_ONE_DRIVE },
+ { "/fdc/read_no_dma_18", test_read_no_dma_18, MACHINE_ONE_DRIVE },
+ { "/fdc/read_no_dma_19", test_read_no_dma_19, MACHINE_ONE_DRIVE },
+ { "/fdc/fuzz-registers", fuzz_registers, MACHINE_ONE_DRIVE },
};
static void run_isolated(const void *data)
{
const FDCTest *test = data;
+ /* Every command line in fdc_tests[] names the pc machine */
if (!qtest_has_machine("pc")) {
g_test_skip("Machine 'pc' is not available");
return;
}
- qtest_start("-machine pc -device floppy,id=floppy0");
+ qtest_start(test->args);
qtest_irq_intercept_in(global_qtest, "ioapic");
test->fn();
--
2.53.0