From: Junjie Cao <[email protected]>

The EHCI tests currently cover port state and hotplug. Drive a
GET_DESCRIPTOR request through the asynchronous schedule and check its
data and completion status. This provides a working control transfer
for the endpoint mismatch tests that follow.

Signed-off-by: Junjie Cao <[email protected]>
---
 tests/qtest/usb-hcd-ehci-test.c | 142 ++++++++++++++++++++++++++++++++
 1 file changed, 142 insertions(+)

diff --git a/tests/qtest/usb-hcd-ehci-test.c b/tests/qtest/usb-hcd-ehci-test.c
index 87e37cdd7c..1a8019dab4 100644
--- a/tests/qtest/usb-hcd-ehci-test.c
+++ b/tests/qtest/usb-hcd-ehci-test.c
@@ -9,6 +9,7 @@
 
 #include "qemu/osdep.h"
 #include "libqtest-single.h"
+#include "libqos/libqos-pc.h"
 #include "libqos/pci-pc.h"
 #include "hw/usb/uhci-regs.h"
 #include "hw/usb/ehci-regs.h"
@@ -20,6 +21,144 @@ static struct qhc uhci2;
 static struct qhc uhci3;
 static struct qhc ehci1;
 
+/* Guest descriptor fields from EHCI sections 3.3, 3.5 and 3.6. */
+#define EHCI_LINK_TERMINATE     1
+#define EHCI_LINK_QH            2
+#define EHCI_QTD_ACTIVE         (1 << 7)
+#define EHCI_QTD_IOC            (1 << 15)
+#define EHCI_QTD_DATA1          (1U << 31)
+#define EHCI_QTD_PID_OUT        0
+#define EHCI_QTD_PID_IN         1
+#define EHCI_QTD_PID_SETUP      2
+#define EHCI_FRAME_NS           1000000
+
+typedef struct EHCITransferTest {
+    QOSState *qs;
+    struct qhc hc;
+    uint32_t opbase;
+    uint64_t mem;
+    uint64_t qh;
+    uint64_t qtd;
+    uint64_t data;
+} EHCITransferTest;
+
+static uint32_t ehci_readl(EHCITransferTest *t, unsigned int reg)
+{
+    return qpci_io_readl(t->hc.dev, t->hc.bar, t->opbase + reg);
+}
+
+static void ehci_writel(EHCITransferTest *t, unsigned int reg, uint32_t value)
+{
+    qpci_io_writel(t->hc.dev, t->hc.bar, t->opbase + reg, value);
+}
+
+static void ehci_stop_schedules(EHCITransferTest *t)
+{
+    unsigned int i;
+
+    ehci_writel(t, USBCMD, USBCMD_RUNSTOP);
+    for (i = 0; i < 10; i++) {
+        qtest_clock_step(t->qs->qts, EHCI_FRAME_NS);
+        if (!(ehci_readl(t, USBSTS) & (USBSTS_ASS | USBSTS_PSS))) {
+            break;
+        }
+    }
+    g_assert_cmpuint(i, <, 10);
+    g_assert_cmphex(ehci_readl(t, USBSTS) & USBSTS_HSE, ==, 0);
+}
+
+static void ehci_transfer_init(EHCITransferTest *t)
+{
+    t->qs = qtest_pc_boot("-machine q35,pit=off -nodefaults "
+                          "-device ich9-usb-ehci1,id=ehci,addr=1d.7 "
+                          "-device usb-kbd,bus=ehci.0,port=1,usb_version=2");
+    qusb_pci_init_one(t->qs->pcibus, &t->hc, QPCI_DEVFN(0x1d, 7), 0);
+    t->opbase = qpci_io_readb(t->hc.dev, t->hc.bar, CAPLENGTH);
+    t->mem = guest_alloc(&t->qs->alloc, 0x2000);
+    t->qh = t->mem;
+    t->qtd = t->mem + 0x100;
+    t->data = t->mem + 0x1000;
+    qtest_memset(t->qs->qts, t->mem, 0, 0x2000);
+
+    ehci_writel(t, USBCMD, USBCMD_HCRESET);
+    ehci_writel(t, CONFIGFLAG, 1);
+    /* PORTSC1 follows CONFIGFLAG. */
+    ehci_writel(t, CONFIGFLAG + 4, PORTSC_PPOWER | PORTSC_PRESET);
+    qtest_clock_step(t->qs->qts, 50 * EHCI_FRAME_NS);
+    ehci_writel(t, CONFIGFLAG + 4, PORTSC_PPOWER);
+    g_assert_cmphex(ehci_readl(t, CONFIGFLAG + 4) &
+                    (PORTSC_CONNECT | PORTSC_PED), ==,
+                    PORTSC_CONNECT | PORTSC_PED);
+    ehci_writel(t, USBSTS, USBSTS_RO_MASK);
+}
+
+static void ehci_control_packet(EHCITransferTest *t, unsigned int pid,
+                                unsigned int len)
+{
+    QTestState *qts = t->qs->qts;
+    uint32_t token;
+    unsigned int i;
+
+    qtest_memset(qts, t->qh, 0, 96);
+    qtest_memset(qts, t->qtd, 0, 64);
+    qtest_writel(qts, t->qh, t->qh | EHCI_LINK_QH);
+    /* EP0, high speed, qTD data toggle, reclamation head, 64-byte packets. */
+    qtest_writel(qts, t->qh + 4, (2 << 12) | (1 << 14) | (1 << 15) |
+                                (64 << 16));
+    qtest_writel(qts, t->qh + 8, 1 << 30);
+    qtest_writel(qts, t->qh + 16, t->qtd);
+    qtest_writel(qts, t->qh + 20, EHCI_LINK_TERMINATE);
+    qtest_writel(qts, t->qtd, EHCI_LINK_TERMINATE);
+    qtest_writel(qts, t->qtd + 4, EHCI_LINK_TERMINATE);
+    token = EHCI_QTD_ACTIVE | EHCI_QTD_IOC | (3 << 10) |
+            (pid << 8) | (len << 16);
+    if (pid != EHCI_QTD_PID_SETUP) {
+        token |= EHCI_QTD_DATA1;
+    }
+    qtest_writel(qts, t->qtd + 8, token);
+    qtest_writel(qts, t->qtd + 12, t->data);
+    ehci_writel(t, USBSTS, USBSTS_RO_MASK);
+    ehci_writel(t, ASYNCLISTADDR, t->qh);
+    ehci_writel(t, USBCMD, USBCMD_RUNSTOP | USBCMD_ASE);
+    for (i = 0; i < 10; i++) {
+        qtest_clock_step(qts, EHCI_FRAME_NS);
+        token = qtest_readl(qts, t->qtd + 8);
+        if (!(token & EHCI_QTD_ACTIVE)) {
+            break;
+        }
+    }
+    g_assert_cmpuint(i, <, 10);
+    g_assert_cmphex(token & 0xff, ==, 0);
+    g_assert_cmpuint((token >> 16) & 0x7fff, ==, 0);
+    g_assert_cmphex(ehci_readl(t, USBSTS) &
+                    (USBSTS_INT | USBSTS_ERRINT | USBSTS_HSE), ==, USBSTS_INT);
+    ehci_stop_schedules(t);
+}
+
+static void test_ehci_control(void)
+{
+    const uint8_t setup[] = { 0x80, 6, 0, 1, 0, 0, 18, 0 };
+    EHCITransferTest t;
+    uint8_t descriptor[18];
+
+    ehci_transfer_init(&t);
+    qtest_memwrite(t.qs->qts, t.data, setup, sizeof(setup));
+    ehci_control_packet(&t, EHCI_QTD_PID_SETUP, sizeof(setup));
+    qtest_memset(t.qs->qts, t.data, 0xa5, sizeof(descriptor));
+    ehci_control_packet(&t, EHCI_QTD_PID_IN, sizeof(descriptor));
+    qtest_memread(t.qs->qts, t.data, descriptor, sizeof(descriptor));
+    g_assert_cmpuint(descriptor[0], ==, sizeof(descriptor));
+    g_assert_cmpuint(descriptor[1], ==, 1);
+    g_assert_cmpuint(descriptor[7], ==, 64);
+    g_assert_cmpuint(descriptor[17], ==, 1);
+    ehci_control_packet(&t, EHCI_QTD_PID_OUT, 0);
+
+    qpci_iounmap(t.hc.dev, t.hc.bar);
+    uhci_deinit(&t.hc);
+    guest_free(&t.qs->alloc, t.mem);
+    qtest_shutdown(t.qs);
+}
+
 /* helpers */
 
 #if 0
@@ -160,6 +299,9 @@ int main(int argc, char **argv)
     qtest_add_func("/ehci/pci/uhci-port-2", pci_uhci_port_2);
     qtest_add_func("/ehci/pci/ehci-port-2", pci_ehci_port_2);
     qtest_add_func("/ehci/pci/ehci-port-3-hotplug", pci_ehci_port_hotplug);
+    if (qtest_has_device("usb-kbd")) {
+        qtest_add_func("/ehci/pci/control", test_ehci_control);
+    }
 
     qtest_start("-machine q35 -device ich9-usb-ehci1,bus=pcie.0,addr=1d.7,"
                 "multifunction=on,id=ich9-ehci-1 "
-- 
2.43.0


Reply via email to