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]> --- 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
