Implement TR NOOP commands by setting up then immediately completing
the packet. Add a TR NOOP test to the xhci qtest.
The IBM AIX XHCI driver uses NOOP commands to check driver and
hardware health.
Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
---
hw/usb/hcd-xhci.c | 28 ++++++++++++++++++++++++++-
tests/qtest/usb-hcd-xhci-test.c | 34 +++++++++++++++++++++++++++++++++
2 files changed, 61 insertions(+), 1 deletion(-)
diff --git a/hw/usb/hcd-xhci.c b/hw/usb/hcd-xhci.c
index d85adaca0d..9e223acd83 100644
--- a/hw/usb/hcd-xhci.c
+++ b/hw/usb/hcd-xhci.c
@@ -1832,6 +1832,20 @@ static int xhci_fire_transfer(XHCIState *xhci, XHCITransfer *xfer, XHCIEPContext
return xhci_submit(xhci, xfer, epctx);
}
+static int xhci_noop_transfer(XHCIState *xhci, XHCITransfer *xfer)
+{
+ /*
+ * TR NOOP conceptually probably better not call into USB subsystem
+ * (usb_packet_setup() via xhci_setup_packet()). In practice it
+ * works and avoids code duplication.
+ */
+ if (xhci_setup_packet(xfer) < 0) {
+ return -1;
+ }
+ xhci_try_complete_packet(xfer);
+ return 0;
+}
+
static void xhci_kick_ep(XHCIState *xhci, unsigned int slotid,
unsigned int epid, unsigned int streamid)
{
@@ -1954,6 +1968,8 @@ static void xhci_kick_epctx(XHCIEPContext *epctx, unsigned int streamid)
epctx->kick_active++;
while (1) {
+ bool noop = false;
+
length = xhci_ring_chain_length(xhci, ring);
if (length <= 0) {
if (epctx->type == ET_ISO_OUT || epctx->type == ET_ISO_IN) {
@@ -1982,10 +1998,20 @@ static void xhci_kick_epctx(XHCIEPContext *epctx, unsigned int streamid)
epctx->kick_active--;
return;
}
+ if (type == TR_NOOP) {
+ noop = true;
+ }
}
xfer->streamid = streamid;
- if (epctx->epid == 1) {
+ if (noop) {
+ if (length != 1) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: NOOP TR TRB within TRB chain!\n", __func__);
+ /* Undefined behavior, we no-op the entire chain */
+ }
+ xhci_noop_transfer(xhci, xfer);
+ } else if (epctx->epid == 1) {
xhci_fire_ctl_transfer(xhci, xfer);
} else {
xhci_fire_transfer(xhci, xfer, epctx);
diff --git a/tests/qtest/usb-hcd-xhci-test.c b/tests/qtest/usb-hcd-xhci-test.c
index d66e76f070..8a36f42522 100644
--- a/tests/qtest/usb-hcd-xhci-test.c
+++ b/tests/qtest/usb-hcd-xhci-test.c
@@ -357,6 +357,30 @@ static void submit_cr_trb(XHCIQState *s, XHCITRB *trb)
xhci_db_writel(s, 0, 0); /* doorbell 0 */
}
+static void submit_tr_trb(XHCIQState *s, int slot, XHCITRB *trb)
+{
+ XHCITRB t;
+ uint64_t tr_addr = s->slots[slot].transfer_ring + s->slots[slot].tr_trb_idx * sizeof(*trb);
+
+ trb->control |= s->slots[slot].tr_trb_c; /* C */
+
+ t.parameter = cpu_to_le64(trb->parameter);
+ t.status = cpu_to_le32(trb->status);
+ t.control = cpu_to_le32(trb->control);
+
+ qtest_memwrite(s->parent->qts, tr_addr, &t, sizeof(t));
+ s->slots[slot].tr_trb_idx++;
+ /* Last entry contains the link, so wrap back */
+ if (s->slots[slot].tr_trb_idx == s->slots[slot].tr_trb_entries - 1) {
+ set_link_trb(s, s->slots[slot].transfer_ring,
+ s->slots[slot].tr_trb_c,
+ s->slots[slot].tr_trb_entries);
+ s->slots[slot].tr_trb_idx = 0;
+ s->slots[slot].tr_trb_c ^= 1;
+ }
+ xhci_db_writel(s, slot, 1); /* doorbell slot, EP0 target */
+}
+
static void pci_xhci_stress_rings(void)
{
XHCIQState *s;
@@ -509,6 +533,16 @@ static void pci_xhci_stress_rings(void)
/* XXX: Check EP state is running? */
+ /* Wrap the transfer ring a few times */
+ for (i = 0; i < 100; i++) {
+ /* Issue a transfer ring slot 0 noop */
+ memset(&trb, 0, sizeof(trb));
+ trb.control |= TR_NOOP << TRB_TYPE_SHIFT;
+ trb.control |= TRB_TR_IOC;
+ submit_tr_trb(s, slotid, &trb);
+ wait_event_trb(s, &trb);
+ }
+
/* Shut it down */
qpci_msix_disable(s->dev);
--
2.45.2
© 2016 - 2024 Red Hat, Inc.