From 818f1682ee4b91bd0083ffcaf648bfc88d9328a9 Mon Sep 17 00:00:00 2001 From: Sebastian Sumpf Date: Tue, 8 Jun 2021 18:35:38 +0200 Subject: [PATCH] qemu-usb: flush EP improve isochronous handling - Patch the XHCI model in order to handle frame wrapping correctly. For this adjust 'mfindex_kick' to the correct period (same, before, or after 'mfindex'). - Flush EP when it is stopped, this causes all pending packets for the EP to be acked. Correct counting of packets in flight. - Add BEI patch by Josef. issue #4196 --- repos/libports/ports/qemu-usb.hash | 2 +- repos/libports/ports/qemu-usb.port | 5 +- repos/libports/src/lib/qemu-usb/host.cc | 47 ++++++--- .../lib/qemu-usb/patches/hcd-xhci-bei.patch | 97 +++++++++++++++++++ .../qemu-usb/patches/xhci_frame_wrap.patch | 48 +++++++++ 5 files changed, 181 insertions(+), 18 deletions(-) create mode 100644 repos/libports/src/lib/qemu-usb/patches/hcd-xhci-bei.patch create mode 100644 repos/libports/src/lib/qemu-usb/patches/xhci_frame_wrap.patch diff --git a/repos/libports/ports/qemu-usb.hash b/repos/libports/ports/qemu-usb.hash index 6a9daef2fa..4a175f28ae 100644 --- a/repos/libports/ports/qemu-usb.hash +++ b/repos/libports/ports/qemu-usb.hash @@ -1 +1 @@ -4b74867ae1e9383a53edb67fc3665ed8b305e2e6 +a716b3ed197d29cb3f059a76de9bb4e0e8c708f0 diff --git a/repos/libports/ports/qemu-usb.port b/repos/libports/ports/qemu-usb.port index 06db1a18d3..6d6ad34e35 100644 --- a/repos/libports/ports/qemu-usb.port +++ b/repos/libports/ports/qemu-usb.port @@ -10,5 +10,8 @@ TAR_OPT(qemu) := --strip-components=1 --files-from - < <(sed 's/-x.x.x/-$(VERSIO HASH_INPUT += $(REP_DIR)/src/lib/qemu-usb/files.list PATCHES := src/lib/qemu-usb/patches/xhci_pci_register.patch \ - src/lib/qemu-usb/patches/usb_bus_nfree.patch + src/lib/qemu-usb/patches/usb_bus_nfree.patch \ + src/lib/qemu-usb/patches/hcd-xhci-bei.patch \ + src/lib/qemu-usb/patches/xhci_frame_wrap.patch + PATCH_OPT:= -p1 diff --git a/repos/libports/src/lib/qemu-usb/host.cc b/repos/libports/src/lib/qemu-usb/host.cc index 9c1eaae3d6..631148d9fd 100644 --- a/repos/libports/src/lib/qemu-usb/host.cc +++ b/repos/libports/src/lib/qemu-usb/host.cc @@ -34,7 +34,6 @@ using Packet_alloc_failed = Usb::Session::Tx::Source::Packet_alloc_failed; using Packet_type = Usb::Packet_descriptor::Type; using Packet_error = Usb::Packet_descriptor::Error; - static unsigned endpoint_number(USBEndpoint const *usb_ep) { bool in = usb_ep->pid == USB_TOKEN_IN; @@ -342,12 +341,8 @@ struct Usb_host_device : List::Element Usb::Packet_descriptor packet = usb_raw.source()->get_acked_packet(); Completion *c = dynamic_cast(packet.completion); - if ((packet.type == Packet_type::ISOC && !packet.read_transfer())) { - free_packet(packet); - _isoch_out_pending--; - continue; - } - if ((c && c->state == Completion::CANCELED)) { + if ((packet.type == Packet_type::ISOC && !packet.read_transfer()) || + (c && c->state == Completion::CANCELED)) { free_packet(packet); continue; } @@ -355,12 +350,11 @@ struct Usb_host_device : List::Element char *content = usb_raw.source()->packet_content(packet); if (packet.type != Packet_type::ISOC) { - c->complete(packet, content); + if (c) c->complete(packet, content); free_packet(packet); } else { /* isochronous in */ free_completion(packet); - _isoc_in_pending--; Isoc_packet *new_packet = new (_alloc) Isoc_packet(packet, content); isoc_read_queue.enqueue(*new_packet); @@ -393,9 +387,7 @@ struct Usb_host_device : List::Element bool isoc_new_packet() { - unsigned count = 0; - isoc_read_queue.for_each([&count] (Isoc_packet&) { count++; }); - return (count + _isoc_in_pending) < 32 ? true : false; + return _isoc_in_pending < 32 ? true : false; } void isoc_in_packet(USBPacket *usb_packet) @@ -403,8 +395,9 @@ struct Usb_host_device : List::Element enum { NUMBER_OF_PACKETS = 1 }; isoc_read(usb_packet); - if (!isoc_new_packet()) + if (!isoc_new_packet()) { return; + } size_t size = usb_packet->ep->max_packet_size * NUMBER_OF_PACKETS; try { @@ -424,7 +417,6 @@ struct Usb_host_device : List::Element c->endpoint = endpoint_number(usb_packet->ep); _isoc_in_pending++; - submit(packet); } catch (Packet_alloc_failed) { if (verbose_warnings) @@ -553,6 +545,11 @@ struct Usb_host_device : List::Element void free_packet(Usb::Packet_descriptor &packet) { + if (packet.type == Packet_type::ISOC) { + if (packet.read_transfer()) _isoc_in_pending--; + else _isoch_out_pending--; + } + free_completion(packet); usb_raw.source()->release_packet(packet); } @@ -654,6 +651,20 @@ struct Usb_host_device : List::Element } } + + void flush_transfers(uint8_t ep) + { + try { + Usb::Packet_descriptor packet = alloc_packet(0, false); + packet.type = Usb::Packet_descriptor::FLUSH_TRANSFERS; + packet.number = ep; + submit(packet); + } catch (...) { + if (verbose_warnings) + warning(__func__, " packet allocation failed"); + } + } + void update_ep(USBDevice *udev) { usb_ep_reset(udev); @@ -877,10 +888,14 @@ static void usb_host_ep_stopped(USBDevice *udev, USBEndpoint *usb_ep) bool in = usb_ep->pid == USB_TOKEN_IN; unsigned ep = endpoint_number(usb_ep); + /* flush pending transfers */ + dev->flush_transfers(ep); + switch (usb_ep->type) { case USB_ENDPOINT_XFER_ISOC: - if (in) - dev->isoc_in_flush(ep); + if (in) { + dev->isoc_in_flush(ep, true); + } default: return; } diff --git a/repos/libports/src/lib/qemu-usb/patches/hcd-xhci-bei.patch b/repos/libports/src/lib/qemu-usb/patches/hcd-xhci-bei.patch new file mode 100644 index 0000000000..fe90467b42 --- /dev/null +++ b/repos/libports/src/lib/qemu-usb/patches/hcd-xhci-bei.patch @@ -0,0 +1,97 @@ +diff --git a/src/lib/qemu/hw/usb/hcd-xhci.c b/src/lib/qemu/hw/usb/hcd-xhci.c +index 9ce7ca7..0e32df5 100644 +--- a/src/lib/qemu/hw/usb/hcd-xhci.c ++++ b/src/lib/qemu/hw/usb/hcd-xhci.c +@@ -307,7 +307,7 @@ static void xhci_kick_epctx(XHCIEPContext *epctx, unsigned int streamid); + static TRBCCode xhci_disable_ep(XHCIState *xhci, unsigned int slotid, + unsigned int epid); + static void xhci_xfer_report(XHCITransfer *xfer); +-static void xhci_event(XHCIState *xhci, XHCIEvent *event, int v); ++static void xhci_event(XHCIState *xhci, XHCIEvent *event, int v, int bei); + static void xhci_write_event(XHCIState *xhci, XHCIEvent *event, int v); + static USBEndpoint *xhci_epid_to_usbep(XHCIEPContext *epctx); + +@@ -458,7 +458,7 @@ static void xhci_mfwrap_timer(void *opaque) + XHCIState *xhci = opaque; + XHCIEvent wrap = { ER_MFINDEX_WRAP, CC_SUCCESS }; + +- xhci_event(xhci, &wrap, 0); ++ xhci_event(xhci, &wrap, 0, 0); + xhci_mfwrap_update(xhci); + } + +@@ -623,7 +623,7 @@ static void xhci_write_event(XHCIState *xhci, XHCIEvent *event, int v) + } + } + +-static void xhci_event(XHCIState *xhci, XHCIEvent *event, int v) ++static void xhci_event(XHCIState *xhci, XHCIEvent *event, int v, int bei) + { + XHCIInterrupter *intr; + dma_addr_t erdp; +@@ -658,7 +658,9 @@ static void xhci_event(XHCIState *xhci, XHCIEvent *event, int v) + xhci_write_event(xhci, event, v); + } + +- xhci_intr_raise(xhci, v); ++ if (!bei) { ++ xhci_intr_raise(xhci, v); ++ } + } + + static void xhci_ring_init(XHCIState *xhci, XHCIRing *ring, +@@ -1395,7 +1397,7 @@ static int xhci_xfer_create_sgl(XHCITransfer *xfer, int in_xfer) + dma_addr_t addr; + unsigned int chunk = 0; + +- if (trb->control & TRB_TR_IOC) { ++ if ((trb->control & TRB_TR_IOC) && !(trb->control & TRB_TR_BEI)) { + xfer->int_req = true; + } + +@@ -1499,7 +1501,8 @@ static void xhci_xfer_report(XHCITransfer *xfer) + DPRINTF("xhci_xfer_data: EDTLA=%d\n", event.length); + edtla = 0; + } +- xhci_event(xhci, &event, TRB_INTR(*trb)); ++ xhci_event(xhci, &event, TRB_INTR(*trb), ++ (trb->control & TRB_TR_BEI)); + reported = 1; + if (xfer->status != CC_SUCCESS) { + return; +@@ -1920,7 +1923,7 @@ static void xhci_kick_epctx(XHCIEPContext *epctx, unsigned int streamid) + ev.slotid = epctx->slotid; + ev.epid = epctx->epid; + ev.ptr = epctx->ring.dequeue; +- xhci_event(xhci, &ev, xhci->slots[epctx->slotid-1].intr); ++ xhci_event(xhci, &ev, xhci->slots[epctx->slotid-1].intr, 0); + } + break; + } +@@ -2539,7 +2542,7 @@ static void xhci_process_commands(XHCIState *xhci) + break; + } + event.slotid = slotid; +- xhci_event(xhci, &event, 0); ++ xhci_event(xhci, &event, 0, 0); + + if (count++ > COMMAND_LIMIT) { + trace_usb_xhci_enforced_limit("commands"); +@@ -2572,7 +2575,7 @@ static void xhci_port_notify(XHCIPort *port, uint32_t bits) + if (!xhci_running(port->xhci)) { + return; + } +- xhci_event(port->xhci, &ev, 0); ++ xhci_event(port->xhci, &ev, 0, 0); + } + + static void xhci_port_update(XHCIPort *port, int is_detach) +@@ -2937,7 +2940,7 @@ static void xhci_oper_write(void *ptr, hwaddr reg, + if (xhci->crcr_low & (CRCR_CA|CRCR_CS) && (xhci->crcr_low & CRCR_CRR)) { + XHCIEvent event = {ER_COMMAND_COMPLETE, CC_COMMAND_RING_STOPPED}; + xhci->crcr_low &= ~CRCR_CRR; +- xhci_event(xhci, &event, 0); ++ xhci_event(xhci, &event, 0, 0); + DPRINTF("xhci: command ring stopped (CRCR=%08x)\n", xhci->crcr_low); + } else { + dma_addr_t base = xhci_addr64(xhci->crcr_low & ~0x3f, val); diff --git a/repos/libports/src/lib/qemu-usb/patches/xhci_frame_wrap.patch b/repos/libports/src/lib/qemu-usb/patches/xhci_frame_wrap.patch new file mode 100644 index 0000000000..346f6d6020 --- /dev/null +++ b/repos/libports/src/lib/qemu-usb/patches/xhci_frame_wrap.patch @@ -0,0 +1,48 @@ +frame index is the frame at the current time, frame kick is the frame the guest +programmed. mfindex is the total of all frames since controller startup. Adjust +mfindex_kick (total frames programmed by the guest) to the correct period, +because frames wrap every 16364 (14 bit). + +diff --git a/src/lib/qemu/hw/usb/hcd-xhci.c b/src/lib/qemu/hw/usb/hcd-xhci.c +index 9ce7ca7..6867eca 100644 +--- a/src/lib/qemu/hw/usb/hcd-xhci.c ++++ b/src/lib/qemu/hw/usb/hcd-xhci.c +@@ -1692,6 +1692,12 @@ static void xhci_calc_intr_kick(XHCIState *xhci, XHCITransfer *xfer, + xfer->mfindex_kick = MAX(asap, kick); + } + ++/* 0 - 16383 (14 Bit from descriptor) */ ++#define FRAME_INDEX(frame) (frame & 0x3fff) ++#define FRAME_PERIOD(frame) (frame & ~0x3fff) ++#define FRAME_PERIOD_NEXT(frame) (FRAME_PERIOD(frame) + 0x4000) ++#define FRAME_PERIOD_LAST(frame) (FRAME_PERIOD(frame) - 0x4000) ++ + static void xhci_calc_iso_kick(XHCIState *xhci, XHCITransfer *xfer, + XHCIEPContext *epctx, uint64_t mfindex) + { +@@ -1705,12 +1711,20 @@ static void xhci_calc_iso_kick(XHCIState *xhci, XHCITransfer *xfer, + xfer->mfindex_kick = asap; + } + } else { +- xfer->mfindex_kick = ((xfer->trbs[0].control >> TRB_TR_FRAMEID_SHIFT) +- & TRB_TR_FRAMEID_MASK) << 3; +- xfer->mfindex_kick |= mfindex & ~0x3fff; +- if (xfer->mfindex_kick + 0x100 < mfindex) { +- xfer->mfindex_kick += 0x4000; ++ unsigned frame_kick = ((xfer->trbs[0].control >> TRB_TR_FRAMEID_SHIFT) ++ & TRB_TR_FRAMEID_MASK) << 3; ++ unsigned frame_index = FRAME_INDEX(mfindex); ++ ++ /* frame index wrapped, kick is still in previous period */ ++ if (frame_index < 1000 && frame_kick > 15000) { ++ xfer->mfindex_kick = frame_kick | FRAME_PERIOD_LAST(mfindex); ++ } ++ /* kick index wrapped, kick index is in next period */ ++ else if (frame_kick < 1000 && frame_index > 15000) { ++ xfer->mfindex_kick = frame_kick | FRAME_PERIOD_NEXT(mfindex); + } ++ else ++ xfer->mfindex_kick = frame_kick | FRAME_PERIOD(mfindex); + } + } +