All of lore.kernel.org
 help / color / mirror / Atom feed
From: Thomas Huth <thuth@redhat.com>
To: qemu-devel@nongnu.org, Richard Henderson <richard.henderson@linaro.org>
Cc: "Junjie Cao" <junjie.cao@intel.com>,
	"Philippe Mathieu-Daudé" <philmd@oss.qualcomm.com>
Subject: [PULL 6/7] tests/qtest/usb-hcd-xhci: test isoch pacing with MFINDEX above 2^32
Date: Thu, 24 Sep 2026 11:05:57 +0200	[thread overview]
Message-ID: <20260924090558.801845-7-thuth@redhat.com> (raw)
In-Reply-To: <20260924090558.801845-1-thuth@redhat.com>

From: Junjie Cao <junjie.cao@intel.com>

Add the minimum needed to drive a transfer ring from the test. Use it to
queue an isoch TD with SIA on usb-audio after stepping the clock past
2^32 microframes, and check that the TD waits for the next interval
boundary. Before the ROUND_UP() change it completed as soon as the
doorbell was rung.

The machine is started with pit=off. With the i8254 present the clock
step takes 26s in an ASan build instead of well under a second.

Signed-off-by: Junjie Cao <junjie.cao@intel.com>
Acked-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Message-ID: <eb57b4b9bd78f08d248fa1ae7ed5d2d5958c9ef4.1789968699.git.junjie.cao@intel.com>
Signed-off-by: Thomas Huth <thuth@redhat.com>
---
 tests/qtest/usb-hcd-xhci-test.c | 283 ++++++++++++++++++++++++++++++++
 1 file changed, 283 insertions(+)

diff --git a/tests/qtest/usb-hcd-xhci-test.c b/tests/qtest/usb-hcd-xhci-test.c
index b58fa1e2dae..61eca37b157 100644
--- a/tests/qtest/usb-hcd-xhci-test.c
+++ b/tests/qtest/usb-hcd-xhci-test.c
@@ -10,8 +10,68 @@
 #include "qemu/osdep.h"
 #include "libqtest-single.h"
 #include "libqos/usb.h"
+#include "libqos/malloc-pc.h"
 #include "qobject/qdict.h"
 
+/* capability registers */
+#define XHCI_CAPLENGTH          0x00
+#define XHCI_HCSPARAMS1         0x04
+#define XHCI_DBOFF              0x14
+#define XHCI_RTSOFF             0x18
+/* operational registers */
+#define XHCI_USBCMD             0x00
+#define XHCI_USBSTS             0x04
+#define XHCI_CRCR               0x18
+#define XHCI_DCBAAP             0x30
+#define XHCI_CONFIG             0x38
+#define XHCI_PORTSC(n)          (0x400 + 0x10 * (n))
+/* interrupter 0, relative to the runtime registers */
+#define XHCI_ERSTSZ             0x28
+#define XHCI_ERSTBA             0x30
+#define XHCI_ERDP               0x38
+
+#define USBCMD_RS               (1 << 0)
+#define USBCMD_HCRST            (1 << 1)
+#define USBSTS_HCH              (1 << 0)
+#define PORTSC_CCS              (1 << 0)
+#define PORTSC_PR               (1 << 4)
+#define PORTSC_PP               (1 << 9)
+#define CRCR_RCS                (1 << 0)
+#define ERDP_EHB                (1 << 3)
+
+#define TRB_C                   (1 << 0)
+#define TRB_TR_IOC              (1 << 5)
+#define TRB_TR_SIA              (1U << 31)
+#define TRB_TYPE(t)             ((t) << 10)
+#define TRB_GET_TYPE(control)   (((control) >> 10) & 0x3f)
+#define TRB_GET_CCODE(status)   ((status) >> 24)
+#define TRB_GET_SLOT(control)   ((control) >> 24)
+
+#define TR_ISOCH                5
+#define CR_ENABLE_SLOT          9
+#define CR_ADDRESS_DEVICE       11
+#define CR_CONFIGURE_ENDPOINT   12
+#define ER_TRANSFER             32
+#define ER_COMMAND_COMPLETE     33
+#define CC_SUCCESS              1
+
+#define EP_TYPE_ISOCH_OUT       1
+#define EP_TYPE_CONTROL         4
+
+#define XHCI_RING_TRBS          64
+#define XHCI_MICROFRAME_NS      125000
+
+typedef struct XHCITest {
+    QTestState *qts;
+    QGuestAllocator alloc;
+    QPCIBus *bus;
+    struct qhc hc;
+    uint32_t oper, runtime, doorbell;
+    uint64_t cmd_ring, event_ring, input_ctx;
+    unsigned int cmd_idx, event_idx;
+    unsigned int port, slot;
+} XHCITest;
+
 static void wait_device_deleted_event(QTestState *qtest, const char *id)
 {
     QDict *resp, *data;
@@ -109,6 +169,227 @@ static void test_usb_ccid_hotplug(void)
     qtest_qmp_device_del(qts, "ccid");
 }
 
+static uint32_t xhci_readl(XHCITest *x, uint32_t off)
+{
+    return qpci_io_readl(x->hc.dev, x->hc.bar, off);
+}
+
+static void xhci_writel(XHCITest *x, uint32_t off, uint32_t val)
+{
+    qpci_io_writel(x->hc.dev, x->hc.bar, off, val);
+}
+
+static void xhci_writeq(XHCITest *x, uint32_t off, uint64_t val)
+{
+    xhci_writel(x, off, val);
+    xhci_writel(x, off + 4, val >> 32);
+}
+
+static uint64_t xhci_alloc_page(XHCITest *x)
+{
+    uint64_t addr = guest_alloc(&x->alloc, 0x1000);
+
+    qtest_memset(x->qts, addr, 0, 0x1000);
+    return addr;
+}
+
+static void xhci_write_trb(XHCITest *x, uint64_t addr, uint64_t parameter,
+                           uint32_t status, uint32_t control)
+{
+    qtest_writeq(x->qts, addr, parameter);
+    qtest_writel(x->qts, addr + 8, status);
+    qtest_writel(x->qts, addr + 12, control);
+}
+
+/* Fetch the next event if there is one. Does not advance the clock. */
+static bool xhci_next_event(XHCITest *x, uint32_t *status, uint32_t *control)
+{
+    uint64_t addr = x->event_ring + 16 * x->event_idx;
+    uint32_t c = qtest_readl(x->qts, addr + 12);
+
+    if (!(c & TRB_C)) {
+        return false;
+    }
+    if (status) {
+        *status = qtest_readl(x->qts, addr + 8);
+    }
+    if (control) {
+        *control = c;
+    }
+    x->event_idx++;
+    g_assert_cmpuint(x->event_idx, <, XHCI_RING_TRBS);
+    xhci_writeq(x, x->runtime + XHCI_ERDP, (addr + 16) | ERDP_EHB);
+    return true;
+}
+
+static unsigned int xhci_command(XHCITest *x, uint64_t parameter,
+                                 uint32_t control)
+{
+    uint32_t status;
+
+    g_assert_cmpuint(x->cmd_idx, <, XHCI_RING_TRBS);
+    xhci_write_trb(x, x->cmd_ring + 16 * x->cmd_idx++, parameter, 0,
+                   control | TRB_C);
+    xhci_writel(x, x->doorbell, 0);
+
+    g_assert_true(xhci_next_event(x, &status, &control));
+    g_assert_cmpuint(TRB_GET_TYPE(control), ==, ER_COMMAND_COMPLETE);
+    g_assert_cmpuint(TRB_GET_CCODE(status), ==, CC_SUCCESS);
+    return TRB_GET_SLOT(control);
+}
+
+/*
+ * Start qemu-xhci with one USB device, run the controller and bring the
+ * device to the Addressed state.
+ */
+static void xhci_test_start(XHCITest *x, const char *usb_device)
+{
+    uint64_t dcbaa, erst, ep0_ring;
+    unsigned int maxports;
+
+    memset(x, 0, sizeof(*x));
+    /* pit=off: a long clock step would run the i8254 timer all the way */
+    x->qts = qtest_initf("-machine pc,pit=off -nodefaults "
+                         "-device qemu-xhci,id=xhci,addr=04.0 %s", usb_device);
+    pc_alloc_init(&x->alloc, x->qts, ALLOC_NO_FLAGS);
+    x->bus = qpci_new_pc(x->qts, NULL);
+    qusb_pci_init_one(x->bus, &x->hc, QPCI_DEVFN(4, 0), 0);
+
+    x->oper = qpci_io_readb(x->hc.dev, x->hc.bar, XHCI_CAPLENGTH);
+    x->runtime = xhci_readl(x, XHCI_RTSOFF) & ~0x1f;
+    x->doorbell = xhci_readl(x, XHCI_DBOFF) & ~0x3;
+    maxports = xhci_readl(x, XHCI_HCSPARAMS1) >> 24;
+
+    xhci_writel(x, x->oper + XHCI_USBCMD, USBCMD_HCRST);
+    g_assert_false(xhci_readl(x, x->oper + XHCI_USBCMD) & USBCMD_HCRST);
+
+    dcbaa = xhci_alloc_page(x);
+    erst = xhci_alloc_page(x);
+    x->cmd_ring = xhci_alloc_page(x);
+    x->event_ring = xhci_alloc_page(x);
+    x->input_ctx = xhci_alloc_page(x);
+
+    xhci_writel(x, x->oper + XHCI_CONFIG, 1);
+    xhci_writeq(x, x->oper + XHCI_DCBAAP, dcbaa);
+    qtest_writeq(x->qts, erst, x->event_ring);
+    qtest_writel(x->qts, erst + 8, XHCI_RING_TRBS);
+    xhci_writel(x, x->runtime + XHCI_ERSTSZ, 1);
+    xhci_writeq(x, x->runtime + XHCI_ERSTBA, erst);
+    xhci_writeq(x, x->runtime + XHCI_ERDP, x->event_ring | ERDP_EHB);
+    xhci_writeq(x, x->oper + XHCI_CRCR, x->cmd_ring | CRCR_RCS);
+    xhci_writel(x, x->oper + XHCI_USBCMD, USBCMD_RS);
+    g_assert_false(xhci_readl(x, x->oper + XHCI_USBSTS) & USBSTS_HCH);
+
+    for (x->port = 0; x->port < maxports; x->port++) {
+        if (xhci_readl(x, x->oper + XHCI_PORTSC(x->port)) & PORTSC_CCS) {
+            break;
+        }
+    }
+    g_assert_cmpuint(x->port, <, maxports);
+    xhci_writel(x, x->oper + XHCI_PORTSC(x->port), PORTSC_PP | PORTSC_PR);
+    while (xhci_next_event(x, NULL, NULL)) {
+        /* drop the port status change events */
+    }
+
+    x->slot = xhci_command(x, 0, TRB_TYPE(CR_ENABLE_SLOT));
+    qtest_writeq(x->qts, dcbaa + 8 * x->slot, xhci_alloc_page(x));
+
+    /* input control context: add slot and ep0 */
+    qtest_writel(x->qts, x->input_ctx + 0x04, 0x3);
+    /* slot context: one context entry, root hub port */
+    qtest_writel(x->qts, x->input_ctx + 0x20, 1 << 27);
+    qtest_writel(x->qts, x->input_ctx + 0x24, (x->port + 1) << 16);
+    /* ep0 context */
+    ep0_ring = xhci_alloc_page(x);
+    qtest_writel(x->qts, x->input_ctx + 0x44,
+                 (64 << 16) | (EP_TYPE_CONTROL << 3));
+    qtest_writeq(x->qts, x->input_ctx + 0x48, ep0_ring | 1);
+    xhci_command(x, x->input_ctx,
+                 TRB_TYPE(CR_ADDRESS_DEVICE) | (x->slot << 24));
+}
+
+/* Returns the address of the transfer ring. */
+static uint64_t xhci_configure_ep(XHCITest *x, unsigned int epid,
+                                  unsigned int type, unsigned int interval,
+                                  unsigned int max_packet)
+{
+    uint64_t ring = xhci_alloc_page(x);
+    uint64_t epctx = x->input_ctx + 0x20 * (epid + 1);
+
+    qtest_memset(x->qts, x->input_ctx, 0, 0x1000);
+    qtest_writel(x->qts, x->input_ctx + 0x04, (1 << epid) | 1);
+    qtest_writel(x->qts, x->input_ctx + 0x20, epid << 27);
+    qtest_writel(x->qts, x->input_ctx + 0x24, (x->port + 1) << 16);
+    qtest_writel(x->qts, epctx + 0x00, interval << 16);
+    qtest_writel(x->qts, epctx + 0x04, (max_packet << 16) | (type << 3));
+    qtest_writeq(x->qts, epctx + 0x08, ring | 1);
+    xhci_command(x, x->input_ctx,
+                 TRB_TYPE(CR_CONFIGURE_ENDPOINT) | (x->slot << 24));
+    return ring;
+}
+
+static void xhci_test_end(XHCITest *x)
+{
+    g_free(x->hc.dev);
+    qpci_free_pc(x->bus);
+    alloc_destroy(&x->alloc);
+    qtest_quit(x->qts);
+}
+
+static bool xhci_test_supported(const char *usb_device)
+{
+    const char *arch = qtest_get_arch();
+
+    if (strcmp(arch, "i386") != 0 && strcmp(arch, "x86_64") != 0) {
+        g_test_skip("Test only runs on x86 (pc machine)");
+        return false;
+    }
+    if (!qtest_has_device("qemu-xhci") || !qtest_has_device(usb_device)) {
+        g_test_skip("Devices not available");
+        return false;
+    }
+    return true;
+}
+
+/*
+ * An isoch TD with SIA set is run at the next interval boundary. That has to
+ * hold once the microframe index no longer fits in 32 bits as well.
+ */
+static void test_xhci_isoch_mfindex_32bit(void)
+{
+    const unsigned int interval = 6;
+    uint32_t control;
+    uint64_t ring;
+    XHCITest x;
+
+    if (!xhci_test_supported("usb-audio")) {
+        return;
+    }
+
+    xhci_test_start(&x, "-audiodev none,id=snd0 "
+                        "-device usb-audio,audiodev=snd0");
+    ring = xhci_configure_ep(&x, 2, EP_TYPE_ISOCH_OUT, interval, 64);
+
+    /* Go past 2^32 microframes and stop off an interval boundary. */
+    qtest_clock_step(x.qts, (1ULL << 32) * XHCI_MICROFRAME_NS);
+    qtest_clock_step(x.qts, 5 * XHCI_MICROFRAME_NS);
+
+    xhci_write_trb(&x, ring, xhci_alloc_page(&x), 64,
+                   TRB_TYPE(TR_ISOCH) | TRB_TR_SIA | TRB_TR_IOC | TRB_C);
+    xhci_writel(&x, x.doorbell + 4 * x.slot, 2);
+    g_assert_false(xhci_next_event(&x, NULL, NULL));
+
+    /*
+     * The streaming interface has not been enabled, so usb-audio stalls the
+     * TD. What matters is when that happens.
+     */
+    qtest_clock_step(x.qts, XHCI_MICROFRAME_NS << interval);
+    g_assert_true(xhci_next_event(&x, NULL, &control));
+    g_assert_cmpuint(TRB_GET_TYPE(control), ==, ER_TRANSFER);
+
+    xhci_test_end(&x);
+}
+
 int main(int argc, char **argv)
 {
     int ret;
@@ -123,6 +404,8 @@ int main(int argc, char **argv)
     if (qtest_has_device("usb-ccid")) {
         qtest_add_func("/xhci/pci/hotplug/usb-ccid", test_usb_ccid_hotplug);
     }
+    qtest_add_func("/xhci/pci/isoch/mfindex-32bit",
+                   test_xhci_isoch_mfindex_32bit);
 
     qtest_start("-device nec-usb-xhci,id=xhci"
                 " -drive id=drive0,if=none,file=null-co://,"
-- 
2.55.0



  parent reply	other threads:[~2026-09-24  9:07 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-24  9:05 [PULL 0/7] XHCI fixes and drop Haiku VM Thomas Huth
2026-09-24  9:05 ` [PULL 1/7] hw/usb/hcd-xhci: Set reentrancy guard in timer functions (CVE-2026-17588) Thomas Huth
2026-09-24  9:05 ` [PULL 2/7] hw/usb/hcd-xhci: Limit DMA transfers to plain memory Thomas Huth
2026-09-24  9:12   ` Philippe Mathieu-Daudé
2026-09-24  9:21     ` Thomas Huth
2026-09-24  9:05 ` [PULL 3/7] tests/vm: Drop the broken Haiku VM Thomas Huth
2026-09-24  9:05 ` [PULL 4/7] hw/usb/hcd-xhci: fix interval alignment after MFINDEX passes 2^32 Thomas Huth
2026-09-24  9:05 ` [PULL 5/7] hw/usb/hcd-xhci: don't assert on NAK when retrying an isoch transfer Thomas Huth
2026-09-24  9:05 ` Thomas Huth [this message]
2026-09-24  9:05 ` [PULL 7/7] tests/qtest/usb-hcd-xhci: test isoch endpoint type mismatch Thomas Huth
2026-09-24 15:24 ` [PULL 0/7] XHCI fixes and drop Haiku VM Richard Henderson

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260924090558.801845-7-thuth@redhat.com \
    --to=thuth@redhat.com \
    --cc=junjie.cao@intel.com \
    --cc=philmd@oss.qualcomm.com \
    --cc=qemu-devel@nongnu.org \
    --cc=richard.henderson@linaro.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.