@samitouri / QOSamiQemu / commits / be1ee6a8f6

tests/qtest/ahci: regression test for ATAPI read vs. drain

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 <den@openvz.org>

Denis V. Lunev committed Jun 19, 2026 at 02:31 UTC be1ee6a8f6e3b7192d57e900497b81a048f8d193
1 file changed +66
tests/qtest/ahci-test.c
+66
@@ -1754,6 +1754,70 @@ static void test_atapi_engine_restart_dma(void)
1754 test_atapi_engine_restart_in_flight(true);
1755 }
1756
1757 +/*
1758 + * Regression test: a multi-sector ATAPI read fetches its later sectors from
1759 + * inside the first read's completion; a concurrent drain (as a guest reset
1760 + * triggers via bdrv_drain_all_begin) must not wedge on that nested read.
1761 + * blkdebug keeps the read in flight across x-blockdev-set-iothread.
1762 + */
1763 +static void test_atapi_drain_in_flight(bool dma)
1764 +{
1765 + AHCIQState *ahci;
1766 + AHCICommand *cmd;
1767 + unsigned char *tx;
1768 + char *iso;
1769 + int fd;
1770 + uint8_t port;
1771 + uint64_t buffer;
1772 + uint16_t bcl = ATAPI_SECTOR_SIZE * 2;
1773 + uint64_t iso_size = (uint64_t)ATAPI_SECTOR_SIZE * 3;
1774 +
1775 + fd = prepare_iso(iso_size, &tx, &iso);
1776 +
1777 + /* 1s read delay: a wide margin so the drain starts before it completes */
1778 + ahci = ahci_boot_and_enable(
1779 + "-blockdev driver=file,node-name=file0,filename=%s,read-only=on "
1780 + "-blockdev driver=blkdebug,node-name=cd0,image=file0,read-only=on,"
1781 + "inject-error.0.event=none,inject-error.0.iotype=read,"
1782 + "inject-error.0.errno=0,inject-error.0.delay-ns=1000000000 "
1783 + "-M q35 "
1784 + "-device ide-cd,drive=cd0 ", iso);
1785 + port = ahci_port_select(ahci);
1786 +
1787 + buffer = ahci_alloc(ahci, bcl);
1788 + qtest_memset(ahci->parent->qts, buffer, 0x00, bcl);
1789 +
1790 + cmd = ahci_atapi_command_create(CMD_ATAPI_READ_10, bcl, dma);
1791 + ahci_command_adjust(cmd, 0, buffer, bcl, 0);
1792 + ahci_command_commit(ahci, cmd, port);
1793 + ahci_command_issue_async(ahci, cmd);
1794 +
1795 + /* Drain (all nodes) while the delayed read is still in flight. */
1796 + qtest_qmp_assert_success(ahci->parent->qts,
1797 + "{ 'execute': 'x-blockdev-set-iothread',"
1798 + " 'arguments': { 'node-name': 'cd0', 'iothread': null,"
1799 + " 'force': true } }");
1800 +
1801 + /* Round-trip through the device to confirm qemu is still alive. */
1802 + ahci_px_rreg(ahci, port, AHCI_PX_TFD);
1803 +
1804 + ahci_command_free(cmd);
1805 + ahci_free(ahci, buffer);
1806 + g_free(tx);
1807 + ahci_shutdown(ahci);
1808 + remove_iso(fd, iso);
1809 +}
1810 +
1811 +static void test_atapi_drain_pio(void)
1812 +{
1813 + test_atapi_drain_in_flight(false);
1814 +}
1815 +
1816 +static void test_atapi_drain_dma(void)
1817 +{
1818 + test_atapi_drain_in_flight(true);
1819 +}
1820 +
1821 /* Regression test: Test that a READ_CD command with a BCL of 0 but a size of 0
1822 * completes as a NOP instead of erroring out. */
1823 static void test_atapi_bcl(void)
@@ -2177,6 +2241,8 @@ int main(int argc, char **argv)
2241 test_atapi_engine_restart_pio);
2242 qtest_add_func("/ahci/cdrom/engine_restart/dma",
2243 test_atapi_engine_restart_dma);
2244 + qtest_add_func("/ahci/cdrom/drain/pio", test_atapi_drain_pio);
2245 + qtest_add_func("/ahci/cdrom/drain/dma", test_atapi_drain_dma);
2246
2247 ret = g_test_run();
2248