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
This commit is contained in:
Sebastian Sumpf 2021-06-08 18:35:38 +02:00 committed by Christian Helmuth
parent eabda8907f
commit 818f1682ee
5 changed files with 181 additions and 18 deletions

View File

@ -1 +1 @@
4b74867ae1e9383a53edb67fc3665ed8b305e2e6
a716b3ed197d29cb3f059a76de9bb4e0e8c708f0

View File

@ -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

View File

@ -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<Usb_host_device>::Element
Usb::Packet_descriptor packet = usb_raw.source()->get_acked_packet();
Completion *c = dynamic_cast<Completion *>(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<Usb_host_device>::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<Usb_host_device>::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<Usb_host_device>::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<Usb_host_device>::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<Usb_host_device>::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<Usb_host_device>::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;
}

View File

@ -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);

View File

@ -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);
}
}