[PULL v2 7/7] tests/qtest/ahci: regression test for ATAPI read vs. drain
"Denis V. Lunev" <[email protected]> Tue, 4 Aug 2026 19:00:14 +0200
| Newsgroups | gmane.comp.emulators.qemu.block,gmane.comp.emulators.qemu |
|---|---|
| Message-ID | <[email protected]> |
Add /ahci/cdrom/drain/{pio,dma}: issue a multi-sector ATAPI read whose
byte-count limit spans two sectors so the device must rebuffer in the
middle of the DRQ burst, hold the backend read in flight with a
blkdebug delay, and fire x-blockdev-set-iothread -- which runs
bdrv_drain_all_begin() exactly like a guest reset does through
virtio_blk_stop_ioeventfd().
On the unfixed PIO path the nested sector fetch is queued behind the
drain and the main loop wedges, so the test hangs. The DMA variant
never rebuffers and serves as a sanity twin.
Signed-off-by: Denis V. Lunev <[email protected]>
CC: Stefan Hajnoczi <[email protected]>
CC: Philippe Mathieu-Daudé <[email protected]>
---
tests/qtest/ahci-test.c | 66 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 66 insertions(+)
diff --git a/tests/qtest/ahci-test.c b/tests/qtest/ahci-test.c
index 58bc04b3ef..71d23fe56a 100644
--- a/tests/qtest/ahci-test.c
+++ b/tests/qtest/ahci-test.c
@@ -1754,6 +1754,70 @@ static void test_atapi_engine_restart_dma(void)
test_atapi_engine_restart_in_flight(true);
}
+/*
+ * Regression test: a multi-sector ATAPI read fetches its later sectors from
+ * inside the first read's completion; a concurrent drain (as a guest reset
+ * triggers via bdrv_drain_all_begin) must not wedge on that nested read.
+ * blkdebug keeps the read in flight across x-blockdev-set-iothread.
+ */
+static void test_atapi_drain_in_flight(bool dma)
+{
+ AHCIQState *ahci;
+ AHCICommand *cmd;
+ unsigned char *tx;
+ char *iso;
+ int fd;
+ uint8_t port;
+ uint64_t buffer;
+ uint16_t bcl = ATAPI_SECTOR_SIZE * 2;
+ uint64_t iso_size = (uint64_t)ATAPI_SECTOR_SIZE * 3;
+
+ fd = prepare_iso(iso_size, &tx, &iso);
+
+ /* 1s read delay: a wide margin so the drain starts before it completes */
+ ahci = ahci_boot_and_enable(
+ "-blockdev driver=file,node-name=file0,filename=%s,read-only=on "
+ "-blockdev driver=blkdebug,node-name=cd0,image=file0,read-only=on,"
+ "inject-error.0.event=none,inject-error.0.iotype=read,"
+ "inject-error.0.errno=0,inject-error.0.delay-ns=1000000000 "
+ "-M q35 "
+ "-device ide-cd,drive=cd0 ", iso);
+ port = ahci_port_select(ahci);
+
+ buffer = ahci_alloc(ahci, bcl);
+ qtest_memset(ahci->parent->qts, buffer, 0x00, bcl);
+
+ cmd = ahci_atapi_command_create(CMD_ATAPI_READ_10, bcl, dma);
+ ahci_command_adjust(cmd, 0, buffer, bcl, 0);
+ ahci_command_commit(ahci, cmd, port);
+ ahci_command_issue_async(ahci, cmd);
+
+ /* Drain (all nodes) while the delayed read is still in flight. */
+ qtest_qmp_assert_success(ahci->parent->qts,
+ "{ 'execute': 'x-blockdev-set-iothread',"
+ " 'arguments': { 'node-name': 'cd0', 'iothread': null,"
+ " 'force': true } }");
+
+ /* Round-trip through the device to confirm qemu is still alive. */
+ ahci_px_rreg(ahci, port, AHCI_PX_TFD);
+
+ ahci_command_free(cmd);
+ ahci_free(ahci, buffer);
+ g_free(tx);
+ ahci_shutdown(ahci);
+ remove_iso(fd, iso);
+}
+
+static void test_atapi_drain_pio(void)
+{
+ test_atapi_drain_in_flight(false);
+}
+
+static void test_atapi_drain_dma(void)
+{
+ test_atapi_drain_in_flight(true);
+}
+
/* Regression test: Test that a READ_CD command with a BCL of 0 but a size of 0
* completes as a NOP instead of erroring out. */
static void test_atapi_bcl(void)
@@ -2177,6 +2241,8 @@ int main(int argc, char **argv)
test_atapi_engine_restart_pio);
qtest_add_func("/ahci/cdrom/engine_restart/dma",
test_atapi_engine_restart_dma);
+ qtest_add_func("/ahci/cdrom/drain/pio", test_atapi_drain_pio);
+ qtest_add_func("/ahci/cdrom/drain/dma", test_atapi_drain_dma);
ret = g_test_run();
--
2.53.0