@samitouri / QOSamiQemu / commits / d05ae87e7a

tests/qtest/ahci: regression test for a PIO write vs. engine stop

Add /ahci/io/pio/engine_stop: hold the backend write of a two-sector PIO write with a blkdebug breakpoint, clear PxCMD.ST so the command list is unmapped underneath it, then let the write complete. The second DRQ phase runs from that completion and reaches ahci_pio_transfer() with no command header. Cc: John Snow <jsnow@redhat.com> Cc: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com> Signed-off-by: Denis V. Lunev <den@openvz.org>

Denis V. Lunev committed Aug 17, 2026 at 23:44 UTC d05ae87e7a6ab519a99f8c44f40d97152adb8b08
1 file changed +72
tests/qtest/ahci-test.c
+72
@@ -1793,6 +1793,76 @@ static void test_atapi_engine_restart_dma(void)
1793 test_atapi_engine_restart_in_flight(true);
1794 }
1795
1796 +/*
1797 + * Regression test: a PIO write outlives the command list it was issued from.
1798 + * ide_cancel_dma_sync() does not reach s->pio_aiocb, so the second DRQ phase
1799 + * runs from the write completion after PxCLB has been unmapped and must not
1800 + * touch the command header any more.
1801 + */
1802 +static void test_write_engine_stop_in_flight(void)
1803 +{
1804 + AHCIQState *ahci;
1805 + AHCICommand *cmd;
1806 + unsigned char *tx;
1807 + unsigned char *rx;
1808 + uint64_t ptr;
1809 + uint8_t port;
1810 + size_t bufsize = AHCI_SECTOR_SIZE * 2;
1811 + size_t i;
1812 +
1813 + ahci = ahci_boot_and_enable("-drive file=blkdebug::%s,if=none,id=drive0,"
1814 + "format=%s,cache=writeback "
1815 + "-M q35 "
1816 + "-device ide-hd,drive=drive0 ",
1817 + tmp_path, imgfmt);
1818 + port = ahci_port_select(ahci);
1819 + ahci_port_clear(ahci, port);
1820 +
1821 + tx = g_malloc(bufsize);
1822 + generate_pattern(tx, bufsize, AHCI_SECTOR_SIZE);
1823 + ptr = ahci_alloc(ahci, bufsize);
1824 + g_assert(ptr);
1825 + qtest_memwrite(ahci->parent->qts, ptr, tx, bufsize);
1826 +
1827 + /* Zero the second sector, which the abandoned command must not reach. */
1828 + rx = g_malloc0(AHCI_SECTOR_SIZE);
1829 + ahci_io(ahci, port, CMD_WRITE_DMA, rx, AHCI_SECTOR_SIZE, 1);
1830 +
1831 + /* Suspend the backend write so the first sector stays in flight. */
1832 + g_free(qtest_hmp(ahci->parent->qts,
1833 + "qemu-io drive0 \"break write_aio wr\""));
1834 +
1835 + cmd = ahci_command_create(CMD_WRITE_PIO);
1836 + ahci_command_adjust(cmd, 0, ptr, bufsize, 0);
1837 + ahci_command_commit(ahci, cmd, port);
1838 + ahci_command_issue_async(ahci, cmd);
1839 +
1840 + /* Drop the command list while the write is still outstanding. */
1841 + ahci_px_clr(ahci, port, AHCI_PX_CMD, AHCI_PX_CMD_ST);
1842 +
1843 + g_free(qtest_hmp(ahci->parent->qts, "qemu-io drive0 \"resume wr\""));
1844 +
1845 + /* Round-trip through the device to confirm qemu is still alive. */
1846 + ahci_px_rreg(ahci, port, AHCI_PX_TFD);
1847 +
1848 + /*
1849 + * The second DRQ phase never fetched its data, so the sector it would
1850 + * have carried has to be untouched rather than hold a copy of the first.
1851 + */
1852 + ahci_px_set(ahci, port, AHCI_PX_CMD, AHCI_PX_CMD_ST);
1853 + memset(rx, 0xff, AHCI_SECTOR_SIZE);
1854 + ahci_io(ahci, port, CMD_READ_DMA, rx, AHCI_SECTOR_SIZE, 1);
1855 + for (i = 0; i < AHCI_SECTOR_SIZE; i++) {
1856 + g_assert_cmpint(rx[i], ==, 0);
1857 + }
1858 +
1859 + ahci_command_free(cmd);
1860 + ahci_free(ahci, ptr);
1861 + g_free(rx);
1862 + g_free(tx);
1863 + ahci_shutdown(ahci);
1864 +}
1865 +
1866 /*
1867 * Regression test: a multi-sector ATAPI read fetches its later sectors from
1868 * inside the first read's completion; a concurrent drain (as a guest reset
@@ -2281,6 +2351,8 @@ int main(int argc, char **argv)
2351 test_atapi_engine_restart_pio);
2352 qtest_add_func("/ahci/cdrom/engine_restart/dma",
2353 test_atapi_engine_restart_dma);
2354 + qtest_add_func("/ahci/io/pio/engine_stop",
2355 + test_write_engine_stop_in_flight);
2356 qtest_add_func("/ahci/cdrom/drain/pio", test_atapi_drain_pio);
2357 qtest_add_func("/ahci/cdrom/drain/dma", test_atapi_drain_dma);
2358