Module Name: src Committed By: skrll Date: Sat Feb 6 10:21:45 UTC 2016
Modified Files: src/sys/dev/usb [nick-nhusb]: ehci.c Log Message: Make interrupt handling MP safe by not dropping the bus lock while traversing the active transfer list. Instead move the complete transfers from the active list to a complete list and do callbacks on this complete list. usbd_transfer_complete can safely drop the bus lock while traversing this private list. To generate a diff of this commit: cvs rdiff -u -r1.234.2.82 -r1.234.2.83 src/sys/dev/usb/ehci.c Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/sys/dev/usb/ehci.c diff -u src/sys/dev/usb/ehci.c:1.234.2.82 src/sys/dev/usb/ehci.c:1.234.2.83 --- src/sys/dev/usb/ehci.c:1.234.2.82 Sat Feb 6 09:02:57 2016 +++ src/sys/dev/usb/ehci.c Sat Feb 6 10:21:45 2016 @@ -1,4 +1,4 @@ -/* $NetBSD: ehci.c,v 1.234.2.82 2016/02/06 09:02:57 skrll Exp $ */ +/* $NetBSD: ehci.c,v 1.234.2.83 2016/02/06 10:21:45 skrll Exp $ */ /* * Copyright (c) 2004-2012 The NetBSD Foundation, Inc. @@ -53,7 +53,7 @@ */ #include <sys/cdefs.h> -__KERNEL_RCSID(0, "$NetBSD: ehci.c,v 1.234.2.82 2016/02/06 09:02:57 skrll Exp $"); +__KERNEL_RCSID(0, "$NetBSD: ehci.c,v 1.234.2.83 2016/02/06 10:21:45 skrll Exp $"); #include "ohci.h" #include "uhci.h" @@ -148,16 +148,20 @@ struct ehci_pipe { }; }; +typedef TAILQ_HEAD(ex_completeq, ehci_xfer) ex_completeq_t; + Static usbd_status ehci_open(struct usbd_pipe *); Static void ehci_poll(struct usbd_bus *); Static void ehci_softintr(void *); Static int ehci_intr1(ehci_softc_t *); Static void ehci_waitintr(ehci_softc_t *, struct usbd_xfer *); -Static void ehci_check_intr(ehci_softc_t *, struct ehci_xfer *); -Static void ehci_check_qh_intr(ehci_softc_t *, struct ehci_xfer *); -Static void ehci_check_itd_intr(ehci_softc_t *, struct ehci_xfer *); -Static void ehci_check_sitd_intr(ehci_softc_t *, struct ehci_xfer *); -Static void ehci_idone(struct ehci_xfer *); +Static void ehci_check_qh_intr(ehci_softc_t *, struct ehci_xfer *, + ex_completeq_t *); +Static void ehci_check_itd_intr(ehci_softc_t *, struct ehci_xfer *, + ex_completeq_t *); +Static void ehci_check_sitd_intr(ehci_softc_t *, struct ehci_xfer *, + ex_completeq_t *); +Static void ehci_idone(struct ehci_xfer *, ex_completeq_t *); Static void ehci_timeout(void *); Static void ehci_timeout_task(void *); Static void ehci_intrlist_timeout(void *); @@ -292,14 +296,19 @@ Static void ehci_dump_exfer(struct ehci #define EHCI_NULL htole32(EHCI_LINK_TERMINATE) -#define ehci_add_intr_list(sc, ex) \ - TAILQ_INSERT_TAIL(&(sc)->sc_intrhead, (ex), ex_next); -#define ehci_del_intr_list(sc, ex) \ - do { \ - TAILQ_REMOVE(&sc->sc_intrhead, (ex), ex_next); \ - (ex)->ex_next.tqe_prev = NULL; \ - } while (0) -#define ehci_active_intr_list(ex) ((ex)->ex_next.tqe_prev != NULL) +static inline void +ehci_add_intr_list(ehci_softc_t *sc, struct ehci_xfer *ex) +{ + + TAILQ_INSERT_TAIL(&sc->sc_intrhead, ex, ex_next); +} + +static inline void +ehci_del_intr_list(ehci_softc_t *sc, struct ehci_xfer *ex) +{ + + TAILQ_REMOVE(&sc->sc_intrhead, ex, ex_next); +} Static const struct usbd_bus_methods ehci_bus_methods = { .ubm_open = ehci_open, @@ -790,15 +799,46 @@ ehci_softintr(void *v) USBHIST_FUNC(); USBHIST_CALLED(ehcidebug); + ex_completeq_t cq; + TAILQ_INIT(&cq); + /* * The only explanation I can think of for why EHCI is as brain dead * as UHCI interrupt-wise is that Intel was involved in both. * An interrupt just tells us that something is done, we have no * clue what, so we need to scan through all active transfers. :-( */ - for (ex = TAILQ_FIRST(&sc->sc_intrhead); ex; ex = nextex) { - nextex = TAILQ_NEXT(ex, ex_next); - ehci_check_intr(sc, ex); + + /* + * ehci_idone will remove transfer from sc->sc_intrhead if it's + * complete and add to our cq list + * */ + TAILQ_FOREACH_SAFE(ex, &sc->sc_intrhead, ex_next, nextex) { + switch (ex->ex_type) { + case EX_CTRL: + case EX_BULK: + case EX_INTR: + ehci_check_qh_intr(sc, ex, &cq); + break; + case EX_ISOC: + ehci_check_itd_intr(sc, ex, &cq); + break; + case EX_FS_ISOC: + ehci_check_sitd_intr(sc, ex, &cq); + break; + default: + KASSERT(false); + } + + } + + TAILQ_FOREACH_SAFE(ex, &cq, ex_next, nextex) { + /* + * XXX transfer_complete memcpys out transfer data (for in + * endpoints) during this call, before methods->done is called. + * A dma sync required beforehand. + */ + usb_transfer_complete(&ex->ex_xfer); } /* Schedule a callout to catch any dropped transactions. */ @@ -813,37 +853,8 @@ ehci_softintr(void *v) } } -/* Check for an interrupt. */ Static void -ehci_check_intr(ehci_softc_t *sc, struct ehci_xfer *ex) -{ - - USBHIST_FUNC(); USBHIST_CALLED(ehcidebug); - USBHIST_LOG(ehcidebug, "ex = %p", ex, 0, 0, 0); - - KASSERT(sc->sc_bus.ub_usepolling || mutex_owned(&sc->sc_lock)); - - switch (ex->ex_type) { - case EX_CTRL: - case EX_BULK: - case EX_INTR: - ehci_check_qh_intr(sc, ex); - break; - case EX_ISOC: - ehci_check_itd_intr(sc, ex); - break; - case EX_FS_ISOC: - ehci_check_sitd_intr(sc, ex); - break; - default: - KASSERT(false); - } - - return; -} - -Static void -ehci_check_qh_intr(ehci_softc_t *sc, struct ehci_xfer *ex) +ehci_check_qh_intr(ehci_softc_t *sc, struct ehci_xfer *ex, ex_completeq_t *cq) { ehci_soft_qtd_t *sqtd, *fsqtd, *lsqtd; uint32_t status; @@ -924,11 +935,11 @@ ehci_check_qh_intr(ehci_softc_t *sc, str done: USBHIST_LOGN(ehcidebug, 10, "ex=%p done", ex, 0, 0, 0); callout_stop(&ex->ex_xfer.ux_callout); - ehci_idone(ex); + ehci_idone(ex, cq); } Static void -ehci_check_itd_intr(ehci_softc_t *sc, struct ehci_xfer *ex) +ehci_check_itd_intr(ehci_softc_t *sc, struct ehci_xfer *ex, ex_completeq_t *cq) { ehci_soft_itd_t *itd; int i; @@ -971,11 +982,11 @@ ehci_check_itd_intr(ehci_softc_t *sc, st done: USBHIST_LOG(ehcidebug, "ex %p done", ex, 0, 0, 0); callout_stop(&ex->ex_xfer.ux_callout); - ehci_idone(ex); + ehci_idone(ex, cq); } void -ehci_check_sitd_intr(ehci_softc_t *sc, struct ehci_xfer *ex) +ehci_check_sitd_intr(ehci_softc_t *sc, struct ehci_xfer *ex, ex_completeq_t *cq) { ehci_soft_sitd_t *sitd; @@ -1009,12 +1020,12 @@ ehci_check_sitd_intr(ehci_softc_t *sc, s USBHIST_LOGN(ehcidebug, 10, "ex=%p done", ex, 0, 0, 0); callout_stop(&(ex->ex_xfer.ux_callout)); - ehci_idone(ex); + ehci_idone(ex, cq); } Static void -ehci_idone(struct ehci_xfer *ex) +ehci_idone(struct ehci_xfer *ex, ex_completeq_t *cq) { struct usbd_xfer *xfer = &ex->ex_xfer; struct ehci_pipe *epipe = EHCI_XFER2EPIPE(xfer); @@ -3264,6 +3275,7 @@ ehci_abort_xfer(struct usbd_xfer *xfer, */ xfer->ux_status = status; /* make software ignore it */ callout_stop(&xfer->ux_callout); + ehci_del_intr_list(sc, exfer); usb_syncmem(&sqh->dma, sqh->offs + offsetof(ehci_qh_t, qh_qtd.qtd_status), @@ -3783,8 +3795,7 @@ ehci_device_ctrl_start(struct usbd_xfer Static void ehci_device_ctrl_done(struct usbd_xfer *xfer) { - struct ehci_xfer *ex = EHCI_XFER2EXFER(xfer); - ehci_softc_t *sc = EHCI_XFER2SC(xfer); + ehci_softc_t *sc __diagused = EHCI_XFER2SC(xfer); struct ehci_pipe *epipe = EHCI_XFER2EPIPE(xfer); usb_device_request_t *req = &xfer->ux_request; int len = UGETW(req->wLength); @@ -3796,14 +3807,11 @@ ehci_device_ctrl_done(struct usbd_xfer * KASSERT(sc->sc_bus.ub_usepolling || mutex_owned(&sc->sc_lock)); KASSERT(xfer->ux_rqflags & URQ_REQUEST); - if (xfer->ux_status != USBD_NOMEM && ehci_active_intr_list(ex)) { - ehci_del_intr_list(sc, ex); /* remove from active list */ - usb_syncmem(&epipe->ctrl.reqdma, 0, sizeof(*req), - BUS_DMASYNC_POSTWRITE); - if (len) - usb_syncmem(&xfer->ux_dmabuf, 0, len, - rd ? BUS_DMASYNC_POSTREAD : BUS_DMASYNC_POSTWRITE); - } + usb_syncmem(&epipe->ctrl.reqdma, 0, sizeof(*req), + BUS_DMASYNC_POSTWRITE); + if (len) + usb_syncmem(&xfer->ux_dmabuf, 0, len, + rd ? BUS_DMASYNC_POSTREAD : BUS_DMASYNC_POSTWRITE); USBHIST_LOG(ehcidebug, "length=%d", xfer->ux_actlen, 0, 0, 0); } @@ -4019,8 +4027,7 @@ ehci_device_bulk_close(struct usbd_pipe Static void ehci_device_bulk_done(struct usbd_xfer *xfer) { - struct ehci_xfer *ex = EHCI_XFER2EXFER(xfer); - ehci_softc_t *sc = EHCI_XFER2SC(xfer); + ehci_softc_t *sc __diagused = EHCI_XFER2SC(xfer); struct ehci_pipe *epipe = EHCI_XFER2EPIPE(xfer); int endpt = epipe->pipe.up_endpoint->ue_edesc->bEndpointAddress; int rd = UE_GET_DIR(endpt) == UE_DIR_IN; @@ -4032,11 +4039,8 @@ ehci_device_bulk_done(struct usbd_xfer * KASSERT(mutex_owned(&sc->sc_lock)); - if (xfer->ux_status != USBD_NOMEM && ehci_active_intr_list(ex)) { - ehci_del_intr_list(sc, ex); /* remove from active list */ - usb_syncmem(&xfer->ux_dmabuf, 0, xfer->ux_length, - rd ? BUS_DMASYNC_POSTREAD : BUS_DMASYNC_POSTWRITE); - } + usb_syncmem(&xfer->ux_dmabuf, 0, xfer->ux_length, + rd ? BUS_DMASYNC_POSTREAD : BUS_DMASYNC_POSTWRITE); USBHIST_LOG(ehcidebug, "length=%d", xfer->ux_actlen, 0, 0, 0); } @@ -4281,10 +4285,9 @@ ehci_device_intr_done(struct usbd_xfer * callout_reset(&xfer->ux_callout, mstohz(xfer->ux_timeout), ehci_timeout, xfer); } - + ehci_add_intr_list(sc, exfer); xfer->ux_status = USBD_IN_PROGRESS; - } else if (xfer->ux_status != USBD_NOMEM && ehci_active_intr_list(exfer)) { - ehci_del_intr_list(sc, exfer); /* remove from active list */ + } else { endpt = epipe->pipe.up_endpoint->ue_edesc->bEndpointAddress; isread = UE_GET_DIR(endpt) == UE_DIR_IN; usb_syncmem(&xfer->ux_dmabuf, 0, xfer->ux_length, @@ -4678,8 +4681,7 @@ ehci_device_fs_isoc_done(struct usbd_xfe KASSERT(mutex_owned(&sc->sc_lock)); epipe->isoc.cur_xfers--; - if (xfer->ux_status != USBD_NOMEM && ehci_active_intr_list(exfer)) { - ehci_del_intr_list(sc, exfer); + if (exfer->ex_isrunning) { ehci_remove_sitd_chain(sc, exfer->ex_itdstart); exfer->ex_isrunning = false; } @@ -5101,8 +5103,7 @@ ehci_device_isoc_done(struct usbd_xfer * KASSERT(mutex_owned(&sc->sc_lock)); epipe->isoc.cur_xfers--; - if (xfer->ux_status != USBD_NOMEM && ehci_active_intr_list(exfer)) { - ehci_del_intr_list(sc, exfer); + if (exfer->ex_isrunning) { ehci_remove_itd_chain(sc, exfer->ex_sitdstart); exfer->ex_isrunning = false; }