All of lore.kernel.org
 help / color / mirror / Atom feed
From: Junjie Cao <junjie.cao@linux.dev>
To: qemu-devel@nongnu.org
Cc: farosas@suse.de, lvivier@redhat.com, pbonzini@redhat.com,
	thuth@redhat.com, peter.maydell@linaro.org, junjie.cao@intel.com
Subject: [PATCH v1 4/7] tests/qtest/usb-hcd-uhci: Test interrupt NAK retries
Date: Thu, 22 Oct 2026 13:45:58 +0000	[thread overview]
Message-ID: <20261022134601.1207204-5-junjie.cao@linux.dev> (raw)
In-Reply-To: <20261022134601.1207204-1-junjie.cao@linux.dev>

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

An idle interrupt endpoint must leave its TD active on NAK. Once
data arrives, completion must clear the NAK bit left by earlier
attempts.

Configure a USB keyboard through EP0, check that an IN TD stays
pending over several frames, then inject a key and verify the report,
completion status and QH advancement. Keeping NAK set on completion
fails this test.

Signed-off-by: Junjie Cao <junjie.cao@intel.com>
---
 tests/qtest/usb-hcd-uhci-test.c | 161 ++++++++++++++++++++++++++++++++
 1 file changed, 161 insertions(+)

diff --git a/tests/qtest/usb-hcd-uhci-test.c b/tests/qtest/usb-hcd-uhci-test.c
index 4446555f08..5c2b6cef7c 100644
--- a/tests/qtest/usb-hcd-uhci-test.c
+++ b/tests/qtest/usb-hcd-uhci-test.c
@@ -8,6 +8,7 @@
  */
 
 #include "qemu/osdep.h"
+#include "qemu/bswap.h"
 #include "libqtest-single.h"
 #include "libqos/libqos.h"
 #include "libqos/usb.h"
@@ -17,6 +18,163 @@
 
 static QOSState *qs;
 
+#define UHCI_FRAME_NS 1000000
+#define UHCI_PID_SETUP 0x2d
+#define UHCI_PID_IN    0x69
+#define UHCI_LINK_END  1
+#define UHCI_LINK_QH   2
+
+typedef struct UHCITransferTest {
+    QOSState *qs;
+    struct qhc hc;
+    uint64_t mem;
+    uint64_t qh;
+    uint64_t td;
+    uint64_t data;
+} UHCITransferTest;
+
+static uint32_t uhci_mem_readl(UHCITransferTest *t, uint64_t addr)
+{
+    uint32_t value;
+
+    qtest_memread(t->qs->qts, addr, &value, sizeof(value));
+    return le32_to_cpu(value);
+}
+
+static void uhci_mem_writel(UHCITransferTest *t, uint64_t addr, uint32_t value)
+{
+    value = cpu_to_le32(value);
+    qtest_memwrite(t->qs->qts, addr, &value, sizeof(value));
+}
+
+static void uhci_writew(UHCITransferTest *t, unsigned int reg, uint16_t value)
+{
+    qpci_io_writew(t->hc.dev, t->hc.bar, reg, value);
+}
+
+static uint32_t uhci_step(UHCITransferTest *t)
+{
+    uint16_t status;
+
+    qtest_clock_step(t->qs->qts, UHCI_FRAME_NS);
+    status = qpci_io_readw(t->hc.dev, t->hc.bar, UHCI_USBSTS);
+    g_assert_cmphex(status & (UHCI_STS_HSERR | UHCI_STS_HCPERR |
+                              UHCI_STS_USBERR), ==, 0);
+    return uhci_mem_readl(t, t->td + 4);
+}
+
+static void uhci_submit(UHCITransferTest *t, unsigned int pid, unsigned int ep,
+                        unsigned int len, bool data1)
+{
+    unsigned int i;
+    uint32_t ctrl = TD_CTRL_ACTIVE | TD_CTRL_IOC | 0x7ff |
+                    (3 << TD_CTRL_ERROR_SHIFT);
+
+    uhci_writew(t, UHCI_USBCMD, 0);
+    uhci_mem_writel(t, t->td, UHCI_LINK_END);
+    uhci_mem_writel(t, t->td + 4, ctrl);
+    uhci_mem_writel(t, t->td + 8, pid | (ep << 15) | (data1 << 19) |
+                                (((len - 1) & 0x7ff) << 21));
+    uhci_mem_writel(t, t->td + 12, t->data);
+    uhci_mem_writel(t, t->qh, UHCI_LINK_END);
+    uhci_mem_writel(t, t->qh + 4, t->td);
+    for (i = 0; i < 1024; i++) {
+        uhci_mem_writel(t, t->mem + 4 * i, t->qh | UHCI_LINK_QH);
+    }
+    uhci_writew(t, UHCI_USBSTS, UHCI_STS_USBINT | UHCI_STS_USBERR);
+    uhci_writew(t, UHCI_USBCMD, UHCI_CMD_RS);
+}
+
+static void uhci_expect_complete(UHCITransferTest *t, unsigned int len)
+{
+    uint32_t ctrl;
+    unsigned int i;
+
+    for (i = 0; i < 10; i++) {
+        ctrl = uhci_step(t);
+        if (!(ctrl & TD_CTRL_ACTIVE)) {
+            break;
+        }
+    }
+    g_assert_cmpuint(i, <, 10);
+    g_assert_cmphex(ctrl & (TD_CTRL_ACTIVE | TD_CTRL_STALL | TD_CTRL_BABBLE |
+                            TD_CTRL_NAK | TD_CTRL_TIMEOUT), ==, 0);
+    g_assert_cmpuint((ctrl + 1) & 0x7ff, ==, len);
+    g_assert_cmphex(uhci_mem_readl(t, t->qh + 4), ==, UHCI_LINK_END);
+    uhci_writew(t, UHCI_USBCMD, 0);
+}
+
+static void uhci_transfer_init(UHCITransferTest *t)
+{
+    const char *cmd = "-nodefaults -global pci-host-bridge.bypass-iommu=on "
+                      "-device piix3-usb-uhci,id=uhci,addr=1d.0 "
+                      "-device usb-kbd,bus=uhci.0,port=1";
+    const uint8_t setup[] = { 0, 9, 1, 0, 0, 0, 0, 0 };
+
+    if (!strcmp(qtest_get_arch(), "ppc64")) {
+        t->qs = qtest_spapr_boot("%s", cmd);
+    } else {
+        t->qs = qtest_pc_boot("-machine pc,pit=off %s", cmd);
+    }
+    qusb_pci_init_one(t->qs->pcibus, &t->hc, QPCI_DEVFN(0x1d, 0), 4);
+    t->mem = guest_alloc(&t->qs->alloc, 0x3000);
+    t->qh = t->mem + 0x1000;
+    t->td = t->mem + 0x1100;
+    t->data = t->mem + 0x2000;
+    qtest_memset(t->qs->qts, t->mem, 0, 0x3000);
+    uhci_writew(t, UHCI_USBCMD, UHCI_CMD_HCRESET);
+    uhci_writew(t, UHCI_USBPORTSC1, UHCI_PORT_RESET);
+    qtest_clock_step(t->qs->qts, 50 * UHCI_FRAME_NS);
+    uhci_writew(t, UHCI_USBPORTSC1, UHCI_PORT_EN);
+    g_assert_cmphex(qpci_io_readw(t->hc.dev, t->hc.bar, UHCI_USBPORTSC1) &
+                    (UHCI_PORT_CCS | UHCI_PORT_EN), ==,
+                    UHCI_PORT_CCS | UHCI_PORT_EN);
+    qpci_io_writel(t->hc.dev, t->hc.bar, UHCI_USBFLBASEADD, t->mem);
+
+    /* Configure the keyboard's interrupt endpoint through EP0 first. */
+    qtest_memwrite(t->qs->qts, t->data, setup, sizeof(setup));
+    uhci_submit(t, UHCI_PID_SETUP, 0, sizeof(setup), false);
+    uhci_expect_complete(t, sizeof(setup));
+    uhci_submit(t, UHCI_PID_IN, 0, 0, true);
+    uhci_expect_complete(t, 0);
+}
+
+static void test_uhci_nak(void)
+{
+    const uint8_t expected[] = { 0, 0, 4, 0, 0, 0, 0, 0 };
+    UHCITransferTest t;
+    uint8_t report[sizeof(expected)];
+    uint32_t ctrl;
+    unsigned int i;
+
+    uhci_transfer_init(&t);
+    qtest_memset(t.qs->qts, t.data, 0xa5, sizeof(report));
+    uhci_submit(&t, UHCI_PID_IN, 1, sizeof(report), false);
+    for (i = 0; i < 3; i++) {
+        ctrl = uhci_step(&t);
+        g_assert_cmphex(ctrl & (TD_CTRL_ACTIVE | TD_CTRL_NAK), ==,
+                        TD_CTRL_NAK | TD_CTRL_ACTIVE);
+        g_assert_cmpuint((ctrl + 1) & 0x7ff, ==, 0);
+    }
+    qtest_memread(t.qs->qts, t.data, report, sizeof(report));
+    for (i = 0; i < sizeof(report); i++) {
+        g_assert_cmphex(report[i], ==, 0xa5);
+    }
+
+    qtest_qmp_assert_success(t.qs->qts,
+        "{'execute': 'input-send-event', 'arguments': {'events': ["
+        "{'type': 'key', 'data': {'down': true, "
+        "'key': {'type': 'qcode', 'data': 'a'}}}]}}");
+    uhci_expect_complete(&t, sizeof(report));
+    qtest_memread(t.qs->qts, t.data, report, sizeof(report));
+    g_assert_cmpmem(report, sizeof(report), expected, sizeof(expected));
+
+    qpci_iounmap(t.hc.dev, t.hc.bar);
+    uhci_deinit(&t.hc);
+    guest_free(&t.qs->alloc, t.mem);
+    qtest_shutdown(t.qs);
+}
+
 static void test_port(int port)
 {
     struct qhc uhci;
@@ -72,6 +230,9 @@ int main(int argc, char **argv)
     if (qtest_has_device("usb-storage")) {
         qtest_add_func("/uhci/pci/hotplug/usb-storage", test_usb_storage_hotplug);
     }
+    if (qtest_has_device("usb-kbd")) {
+        qtest_add_func("/uhci/pci/intr-nak", test_uhci_nak);
+    }
 
     if (strcmp(arch, "i386") == 0 || strcmp(arch, "x86_64") == 0) {
         qs = qtest_pc_boot("%s", cmd);
-- 
2.43.0



  parent reply	other threads:[~2026-10-22 13:47 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-22 13:45 [PATCH v1 0/7] tests/qtest: Extend USB host controller transfer coverage Junjie Cao
2026-10-22 13:45 ` [PATCH v1 1/7] tests/qtest/usb-hcd-ehci: Test a control transfer Junjie Cao
2026-10-22 13:45 ` [PATCH v1 2/7] tests/qtest/usb-hcd-ehci: Cover isochronous IN to EP0 Junjie Cao
2026-10-22 13:45 ` [PATCH v1 3/7] tests/qtest/usb-hcd-ehci: Cover isochronous OUT " Junjie Cao
2026-10-22 13:45 ` Junjie Cao [this message]
2026-10-22 13:45 ` [PATCH v1 5/7] tests/qtest/usb-hcd-uhci: Test isochronous NAK completion Junjie Cao
2026-10-22 13:46 ` [PATCH v1 6/7] tests/qtest/usb-hcd-uhci: Test a successful isochronous transfer Junjie Cao
2026-10-22 13:46 ` [PATCH v1 7/7] tests/qtest/usb-hcd-xhci: " Junjie Cao

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=20261022134601.1207204-5-junjie.cao@linux.dev \
    --to=junjie.cao@linux.dev \
    --cc=farosas@suse.de \
    --cc=junjie.cao@intel.com \
    --cc=lvivier@redhat.com \
    --cc=pbonzini@redhat.com \
    --cc=peter.maydell@linaro.org \
    --cc=qemu-devel@nongnu.org \
    --cc=thuth@redhat.com \
    /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.