diff options
author | Alexandre Ratchov <ratchov@cvs.openbsd.org> | 2019-04-10 08:27:36 +0000 |
---|---|---|
committer | Alexandre Ratchov <ratchov@cvs.openbsd.org> | 2019-04-10 08:27:36 +0000 |
commit | f7df5a6d34d0e543c0e5af2b14e835f398aac61c (patch) | |
tree | d4e74961b5b1e5b0395b82fac44d7b57e9dd22bf /sys/dev | |
parent | 622b70b4b1064b04601bfb437aa6f697a5d60c31 (diff) |
Handle missed service errors, specific to isochronous transfers.
After each MSE, ensuire usbd_complete_transfer() is called for each
missed transfer, for which there's no transfer completion event. Fixes
crashes and deadlocks in upper layers caused by the missing
completion.
ok deraadt, patrick; help from mpi, patrick, gmlocker
Diffstat (limited to 'sys/dev')
-rw-r--r-- | sys/dev/usb/xhci.c | 53 |
1 files changed, 52 insertions, 1 deletions
diff --git a/sys/dev/usb/xhci.c b/sys/dev/usb/xhci.c index 149ce7bbb3b..89757f984d0 100644 --- a/sys/dev/usb/xhci.c +++ b/sys/dev/usb/xhci.c @@ -1,4 +1,4 @@ -/* $OpenBSD: xhci.c,v 1.101 2019/03/17 22:05:37 mglocker Exp $ */ +/* $OpenBSD: xhci.c,v 1.102 2019/04/10 08:27:35 ratchov Exp $ */ /* * Copyright (c) 2014-2015 Martin Pieuchot @@ -72,6 +72,7 @@ struct xhci_pipe { struct usbd_xfer *aborted_xfer; int halted; size_t free_trbs; + int skip; }; int xhci_reset(struct xhci_softc *); @@ -697,6 +698,36 @@ xhci_event_dequeue(struct xhci_softc *sc) } void +xhci_skip_all(struct xhci_pipe *xp) +{ + struct usbd_xfer *xfer, *last; + + if (xp->skip) { + /* + * Find the last transfer to skip, this is necessary + * as xhci_xfer_done() posts new transfers which we + * don't want to skip + */ + last = SIMPLEQ_FIRST(&xp->pipe.queue); + if (last == NULL) + goto done; + while ((xfer = SIMPLEQ_NEXT(last, next)) != NULL) + last = xfer; + + do { + xfer = SIMPLEQ_FIRST(&xp->pipe.queue); + if (xfer == NULL) + goto done; + DPRINTF(("%s: skipping %p\n", __func__, xfer)); + xfer->status = USBD_NORMAL_COMPLETION; + xhci_xfer_done(xfer); + } while (xfer != last); + done: + xp->skip = 0; + } +} + +void xhci_event_xfer(struct xhci_softc *sc, uint64_t paddr, uint32_t status, uint32_t flags) { @@ -726,10 +757,17 @@ xhci_event_xfer(struct xhci_softc *sc, uint64_t paddr, uint32_t status, case XHCI_CODE_RING_UNDERRUN: DPRINTF(("%s: slot %u underrun with %zu TRB\n", DEVNAME(sc), slot, xp->ring.ntrb - xp->free_trbs)); + xhci_skip_all(xp); return; case XHCI_CODE_RING_OVERRUN: DPRINTF(("%s: slot %u overrun with %zu TRB\n", DEVNAME(sc), slot, xp->ring.ntrb - xp->free_trbs)); + xhci_skip_all(xp); + return; + case XHCI_CODE_MISSED_SRV: + DPRINTF(("%s: slot %u missed srv with %zu TRB\n", DEVNAME(sc), + slot, xp->ring.ntrb - xp->free_trbs)); + xp->skip = 1; return; default: break; @@ -852,6 +890,7 @@ int xhci_event_xfer_isoc(struct usbd_xfer *xfer, struct xhci_pipe *xp, uint32_t remain, int trb_idx) { + struct usbd_xfer *skipxfer; struct xhci_xfer *xx = (struct xhci_xfer *)xfer; int trb0_idx, frame_idx = 0; @@ -893,6 +932,18 @@ xhci_event_xfer_isoc(struct usbd_xfer *xfer, struct xhci_pipe *xp, if (xx->index != trb_idx) return (1); + if (xp->skip) { + while (1) { + skipxfer = SIMPLEQ_FIRST(&xp->pipe.queue); + if (skipxfer == xfer || xfer == NULL) + break; + DPRINTF(("%s: skipping %p\n", __func__, skipxfer)); + skipxfer->status = USBD_NORMAL_COMPLETION; + xhci_xfer_done(skipxfer); + } + xp->skip = 0; + } + xfer->status = USBD_NORMAL_COMPLETION; return (0); |