@samitouri / QOSamiQemu / commits / cb7bc10385

tests/qtest/ahci: test ATAPI read completing after engine restart

Add a regression test for the crash that occurs when a buffered ATAPI read completes after the command engine has been restarted. Issue an ATAPI READ_10 against a blkdebug-backed CD, suspend the backend read so it stays in flight, stop and restart the port's command engine (which re-maps the command list and clears cur_cmd), then release the read. The PIO and DMA reply paths fault in different AHCI helpers (ahci_pio_transfer() vs ahci_dma_rw_buf()), so cover both. The DMA variant is the reliable guard: on engine restart check_cmd() can re-arm cur_cmd before the old read completes, so the PIO variant does not fault in every build. The test only asserts that qemu survives a subsequent register access; if the blkdebug breakpoint ever failed to park the read it would pass without exercising the bug, as with the existing break/resume tests. Signed-off-by: Denis V. Lunev <den@openvz.org> Message-ID: <20260619112158.304782-3-den@openvz.org> Signed-off-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>

Denis V. Lunev committed Jun 19, 2026 at 13:21 UTC cb7bc10385f365c0792b77fc73b1d17a16cbaefd
1 file changed +67
tests/qtest/ahci-test.c
+67
@@ -1625,6 +1625,69 @@ static void test_cdrom_pio_multi(void)
1625 ahci_test_cdrom_read10(3, false);
1626 }
1627
1628 +/*
1629 + * Regression test: a buffered ATAPI read completing after a command
1630 + * engine restart must not dereference the cleared cur_cmd. Cover both
1631 + * PIO and DMA; the DMA variant is the reliable guard.
1632 + */
1633 +static void test_atapi_engine_restart_in_flight(bool dma)
1634 +{
1635 + AHCIQState *ahci;
1636 + AHCICommand *cmd;
1637 + unsigned char *tx;
1638 + char *iso;
1639 + int fd;
1640 + uint8_t port;
1641 + uint64_t buffer;
1642 + uint64_t iso_size = (uint64_t)ATAPI_SECTOR_SIZE * 2;
1643 +
1644 + fd = prepare_iso(iso_size, &tx, &iso);
1645 +
1646 + ahci = ahci_boot_and_enable("-drive if=none,id=drive0,"
1647 + "file=blkdebug::%s,format=raw,readonly=on "
1648 + "-M q35 "
1649 + "-device ide-cd,drive=drive0 ", iso);
1650 + port = ahci_port_select(ahci);
1651 +
1652 + buffer = ahci_alloc(ahci, ATAPI_SECTOR_SIZE);
1653 + qtest_memset(ahci->parent->qts, buffer, 0x00, ATAPI_SECTOR_SIZE);
1654 +
1655 + /* Suspend the next backend read so the ATAPI read stays in flight. */
1656 + g_free(qtest_hmp(ahci->parent->qts,
1657 + "qemu-io drive0 \"break read_aio rd\""));
1658 +
1659 + cmd = ahci_atapi_command_create(CMD_ATAPI_READ_10, ATAPI_SECTOR_SIZE,
1660 + dma);
1661 + ahci_command_adjust(cmd, 0, buffer, ATAPI_SECTOR_SIZE, 0);
1662 + ahci_command_commit(ahci, cmd, port);
1663 + ahci_command_issue_async(ahci, cmd);
1664 +
1665 + /* Stop and restart the command engine to re-map the command list. */
1666 + ahci_px_clr(ahci, port, AHCI_PX_CMD, AHCI_PX_CMD_ST);
1667 + ahci_px_set(ahci, port, AHCI_PX_CMD, AHCI_PX_CMD_ST);
1668 +
1669 + g_free(qtest_hmp(ahci->parent->qts, "qemu-io drive0 \"resume rd\""));
1670 +
1671 + /* Round-trip through the device to confirm qemu is still alive. */
1672 + ahci_px_rreg(ahci, port, AHCI_PX_TFD);
1673 +
1674 + ahci_command_free(cmd);
1675 + ahci_free(ahci, buffer);
1676 + g_free(tx);
1677 + ahci_shutdown(ahci);
1678 + remove_iso(fd, iso);
1679 +}
1680 +
1681 +static void test_atapi_engine_restart_pio(void)
1682 +{
1683 + test_atapi_engine_restart_in_flight(false);
1684 +}
1685 +
1686 +static void test_atapi_engine_restart_dma(void)
1687 +{
1688 + test_atapi_engine_restart_in_flight(true);
1689 +}
1690 +
1691 /* Regression test: Test that a READ_CD command with a BCL of 0 but a size of 0
1692 * completes as a NOP instead of erroring out. */
1693 static void test_atapi_bcl(void)
@@ -2042,6 +2105,10 @@ int main(int argc, char **argv)
2105
2106 qtest_add_func("/ahci/cdrom/pio/bcl", test_atapi_bcl);
2107 qtest_add_func("/ahci/cdrom/eject", test_atapi_tray);
2108 + qtest_add_func("/ahci/cdrom/engine_restart/pio",
2109 + test_atapi_engine_restart_pio);
2110 + qtest_add_func("/ahci/cdrom/engine_restart/dma",
2111 + test_atapi_engine_restart_dma);
2112
2113 ret = g_test_run();
2114