From: Junjie Cao <[email protected]>

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 <[email protected]>
---
 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


Reply via email to