Index: ehci.c =================================================================== RCS file: /home/ncvs/src/sys/dev/usb/ehci.c,v retrieving revision 1.19 diff -u -r1.19 ehci.c --- ehci.c 12 Nov 2004 02:57:35 -0000 1.19 +++ ehci.c 12 Nov 2004 03:26:29 -0000 @@ -754,7 +754,15 @@ } done: DPRINTFN(12, ("ehci_check_intr: ex=%p done\n", ex)); - usb_uncallout(ex->xfer.timeout_handle, ehci_timeout, ex); + /* The timeout may have fired already but not yet run. */ + if (ex->xfer.timeout_active && usb_uncallout(ex->xfer.timeout_handle, + ehci_timeout, &ex->xfer) == 0) { + printf("ehci: timeout race avoided\n"); + /* Make ehci_idone() ignore this xfer. */ + ex->xfer.status = USBD_TIMEOUT; + } else { + ex->xfer.timeout_active = 0; + } ehci_idone(ex); } @@ -1169,6 +1177,8 @@ } if (xfer != NULL) { memset(xfer, 0, sizeof(struct ehci_xfer)); + usb_init_task(&EXFER(xfer)->abort_task, ehci_timeout_task, + EXFER(xfer)); #ifdef DIAGNOSTIC EXFER(xfer)->isdone = 1; xfer->busy_free = XFER_BUSY; @@ -2491,11 +2501,38 @@ DPRINTF(("ehci_abort_xfer: xfer=%p pipe=%p\n", xfer, epipe)); + + /* + * Step 1: Make interrupt routine, timers and hardware ignore xfer. + */ + s = splusb(); + xfer->status = status; /* make software ignore it */ + qhstatus = sqh->qh.qh_qtd.qtd_status; + if (!sc->sc_dying) { + /* Disable the xfer in hardware to prevent interrupts. */ + sqh->qh.qh_qtd.qtd_status = qhstatus | htole32(EHCI_QTD_HALTED); + for (sqtd = exfer->sqtdstart; ; sqtd = sqtd->nextqtd) { + sqtd->qtd.qtd_status |= htole32(EHCI_QTD_HALTED); + if (sqtd == exfer->sqtdend) + break; + } + } + if (status == USBD_CANCELLED) { + /* + * Stop the timeout timer, waiting if required. We can + * guarantee that interrupts or timeouts will not complete + * the the xfer while we wait, because both ignore transfers + * with a status of USBD_CANCELLED. + */ + usb_rem_task(xfer->pipe->device, &EXFER(xfer)->abort_task); + usb_uncallout_drain(xfer->timeout_handle, ehci_timeout, xfer); + xfer->timeout_active = 0; + } + splx(s); + if (sc->sc_dying) { /* If we're dying, just do the software part. */ s = splusb(); - xfer->status = status; /* make software ignore it */ - usb_uncallout(xfer->timeout_handle, ehci_timeout, xfer); usb_transfer_complete(xfer); splx(s); return; @@ -2505,21 +2542,6 @@ panic("ehci_abort_xfer: not in process context"); /* - * Step 1: Make interrupt routine and hardware ignore xfer. - */ - s = splusb(); - xfer->status = status; /* make software ignore it */ - usb_uncallout(xfer->timeout_handle, ehci_timeout, xfer); - qhstatus = sqh->qh.qh_qtd.qtd_status; - sqh->qh.qh_qtd.qtd_status = qhstatus | htole32(EHCI_QTD_HALTED); - for (sqtd = exfer->sqtdstart; ; sqtd = sqtd->nextqtd) { - sqtd->qtd.qtd_status |= htole32(EHCI_QTD_HALTED); - if (sqtd == exfer->sqtdend) - break; - } - splx(s); - - /* * Step 2: Wait until we know hardware has finished any possible * use of the xfer. Also make sure the soft interrupt routine * has run. @@ -2584,6 +2606,14 @@ if (ehcidebug > 1) usbd_dump_pipe(exfer->xfer.pipe); #endif + exfer->xfer.timeout_active = 0; + + /* + * When a timeout happens concurrently with ehci_abort_xfer(), let + * the non-timeout process do the completion. + */ + if (exfer->xfer.status == USBD_CANCELLED) + return; if (sc->sc_dying) { ehci_abort_xfer(&exfer->xfer, USBD_TIMEOUT); @@ -2591,7 +2621,7 @@ } /* Execute the abort in a process context. */ - usb_init_task(&exfer->abort_task, ehci_timeout_task, addr); + exfer->xfer.status = USBD_TIMEOUT; usb_add_task(exfer->xfer.pipe->device, &exfer->abort_task); } @@ -2814,6 +2844,7 @@ if (xfer->timeout && !sc->sc_bus.use_polling) { usb_callout(xfer->timeout_handle, MS_TO_TICKS(xfer->timeout), ehci_timeout, xfer); + xfer->timeout_active = 1; } ehci_add_intr_list(sc, exfer); xfer->status = USBD_IN_PROGRESS; @@ -2924,6 +2955,7 @@ if (xfer->timeout && !sc->sc_bus.use_polling) { usb_callout(xfer->timeout_handle, MS_TO_TICKS(xfer->timeout), ehci_timeout, xfer); + xfer->timeout_active = 1; } ehci_add_intr_list(sc, exfer); xfer->status = USBD_IN_PROGRESS; @@ -3093,6 +3125,7 @@ if (xfer->timeout && !sc->sc_bus.use_polling) { usb_callout(xfer->timeout_handle, MS_TO_TICKS(xfer->timeout), ehci_timeout, xfer); + xfer->timeout_active = 1; } ehci_add_intr_list(sc, exfer); xfer->status = USBD_IN_PROGRESS; @@ -3187,6 +3220,7 @@ if (xfer->timeout && !sc->sc_bus.use_polling) { usb_callout(xfer->timeout_handle, MS_TO_TICKS(xfer->timeout), ehci_timeout, xfer); + xfer->timeout_active = 1; } splx(s); Index: ohci.c =================================================================== RCS file: /home/ncvs/src/sys/dev/usb/ohci.c,v retrieving revision 1.146 diff -u -r1.146 ohci.c --- ohci.c 12 Nov 2004 02:57:35 -0000 1.146 +++ ohci.c 12 Nov 2004 03:26:30 -0000 @@ -998,6 +998,8 @@ } if (xfer != NULL) { memset(xfer, 0, sizeof (struct ohci_xfer)); + usb_init_task(&OXFER(xfer)->abort_task, ohci_timeout_task, + OXFER(xfer)); #ifdef DIAGNOSTIC xfer->busy_free = XFER_BUSY; #endif @@ -1397,6 +1399,15 @@ */ continue; } + /* The timeout may have fired already but not yet run. */ + if (xfer->timeout_active && usb_uncallout(xfer->timeout_handle, + ohci_timeout, xfer) == 0) { + printf("ohci: timeout race avoided\n"); + /* Ignore and let the timer handle it. */ + xfer->status = USBD_TIMEOUT; + } else { + xfer->timeout_active = 0; + } if (xfer->status == USBD_CANCELLED || xfer->status == USBD_TIMEOUT) { DPRINTF(("ohci_process_done: cancel/timeout %p\n", @@ -1404,7 +1415,6 @@ /* Handled by abort routine. */ continue; } - usb_uncallout(xfer->timeout_handle, ohci_timeout, xfer); len = std->len; if (std->td.td_cbp != 0) @@ -1804,6 +1814,7 @@ if (xfer->timeout && !sc->sc_bus.use_polling) { usb_callout(xfer->timeout_handle, MS_TO_TICKS(xfer->timeout), ohci_timeout, xfer); + xfer->timeout_active = 1; } splx(s); @@ -1968,6 +1979,14 @@ ohci_softc_t *sc = (ohci_softc_t *)opipe->pipe.device->bus; DPRINTF(("ohci_timeout: oxfer=%p\n", oxfer)); + oxfer->xfer.timeout_active = 0; + + /* + * When a timeout happens concurrently with uhci_abort_xfer(), let + * the non-timeout process do the completion. + */ + if (oxfer->xfer.status == USBD_CANCELLED) + return; if (sc->sc_dying) { ohci_abort_xfer(&oxfer->xfer, USBD_TIMEOUT); @@ -1975,7 +1994,7 @@ } /* Execute the abort in a process context. */ - usb_init_task(&oxfer->abort_task, ohci_timeout_task, addr); + oxfer->xfer.status = USBD_TIMEOUT; usb_add_task(oxfer->xfer.pipe->device, &oxfer->abort_task); } @@ -2251,11 +2270,32 @@ DPRINTF(("ohci_abort_xfer: xfer=%p pipe=%p sed=%p\n", xfer, opipe,sed)); + /* + * Step 1: Make interrupt routine, timers and hardware ignore xfer. + */ + s = splusb(); + xfer->status = status; /* make software ignore it */ + if (!sc->sc_dying) { + DPRINTFN(1,("ohci_abort_xfer: stop ed=%p\n", sed)); + /* force hardware skip */ + sed->ed.ed_flags |= htole32(OHCI_ED_SKIP); + } + if (status == USBD_CANCELLED) { + /* + * Stop the timeout timer, waiting if required. We can + * guarantee that interrupts or timeouts will not complete + * the the xfer while we wait, because both ignore transfers + * with a status of USBD_CANCELLED. + */ + usb_rem_task(xfer->pipe->device, &OXFER(xfer)->abort_task); + usb_uncallout_drain(xfer->timeout_handle, ohci_timeout, xfer); + xfer->timeout_active = 0; + } + splx(s); + if (sc->sc_dying) { /* If we're dying, just do the software part. */ s = splusb(); - xfer->status = status; /* make software ignore it */ - usb_uncallout(xfer->timeout_handle, ohci_timeout, xfer); usb_transfer_complete(xfer); splx(s); } @@ -2264,16 +2304,6 @@ panic("ohci_abort_xfer: not in process context"); /* - * Step 1: Make interrupt routine and hardware ignore xfer. - */ - s = splusb(); - xfer->status = status; /* make software ignore it */ - usb_uncallout(xfer->timeout_handle, ohci_timeout, xfer); - splx(s); - DPRINTFN(1,("ohci_abort_xfer: stop ed=%p\n", sed)); - sed->ed.ed_flags |= htole32(OHCI_ED_SKIP); /* force hardware skip */ - - /* * Step 2: Wait until we know hardware has finished any possible * use of the xfer. Also make sure the soft interrupt routine * has run. @@ -2984,6 +3014,7 @@ if (xfer->timeout && !sc->sc_bus.use_polling) { usb_callout(xfer->timeout_handle, MS_TO_TICKS(xfer->timeout), ohci_timeout, xfer); + xfer->timeout_active = 1; } #if 0 Index: uhci.c =================================================================== RCS file: /home/ncvs/src/sys/dev/usb/uhci.c,v retrieving revision 1.158 diff -u -r1.158 uhci.c --- uhci.c 12 Nov 2004 02:57:35 -0000 1.158 +++ uhci.c 12 Nov 2004 03:26:30 -0000 @@ -1369,7 +1369,15 @@ } done: DPRINTFN(12, ("uhci_check_intr: ii=%p done\n", ii)); - usb_uncallout(ii->xfer->timeout_handle, uhci_timeout, ii); + /* The timeout may have fired already but not yet run. */ + if (ii->xfer->timeout_active && usb_uncallout(ii->xfer->timeout_handle, + uhci_timeout, ii) == 0) { + printf("uhci: timeout race avoided\n"); + /* Make uhci_idone() ignore this xfer. */ + ii->xfer->status = USBD_TIMEOUT; + } else { + ii->xfer->timeout_active = 0; + } uhci_idone(ii); } @@ -1495,6 +1503,7 @@ } end: + usb_rem_task(xfer->pipe->device, &UXFER(xfer)->abort_task); usb_transfer_complete(xfer); DPRINTFN(12, ("uhci_idone: ii=%p done\n", ii)); } @@ -1511,6 +1520,14 @@ uhci_softc_t *sc = (uhci_softc_t *)upipe->pipe.device->bus; DPRINTF(("uhci_timeout: uxfer=%p\n", uxfer)); + ii->xfer->timeout_active = 0; + + /* + * When a timeout happens concurrently with ehci_abort_xfer(), let + * the non-timeout process do the completion. + */ + if (ii->xfer->status == USBD_CANCELLED) + return; if (sc->sc_dying) { uhci_abort_xfer(&uxfer->xfer, USBD_TIMEOUT); @@ -1518,6 +1535,7 @@ } /* Execute the abort in a process context. */ + uxfer->xfer.status = USBD_TIMEOUT; usb_add_task(uxfer->xfer.pipe->device, &uxfer->abort_task); } @@ -1894,6 +1912,7 @@ if (xfer->timeout && !sc->sc_bus.use_polling) { usb_callout(xfer->timeout_handle, MS_TO_TICKS(xfer->timeout), uhci_timeout, ii); + xfer->timeout_active = 1; } xfer->status = USBD_IN_PROGRESS; splx(s); @@ -1940,13 +1959,36 @@ DPRINTFN(1,("uhci_abort_xfer: xfer=%p, status=%d\n", xfer, status)); + /* + * Step 1: Make interrupt routine, timers and hardware ignore xfer. + */ + s = splusb(); + xfer->status = status; /* make software ignore it */ + if (!sc->sc_dying) { + /* Disable the xfer in hardware to prevent interrupts. */ + DPRINTFN(1,("uhci_abort_xfer: stop ii=%p\n", ii)); + for (std = ii->stdstart; std != NULL; std = std->link.std) + std->td.td_status &= htole32(~(UHCI_TD_ACTIVE | + UHCI_TD_IOC)); + } + if (status == USBD_CANCELLED) { + /* + * Stop the timeout timer, waiting if required. We can + * guarantee that interrupts or timeouts will not complete + * the the xfer while we wait, because both ignore transfers + * with a status of USBD_CANCELLED. + */ + usb_rem_task(xfer->pipe->device, &UXFER(xfer)->abort_task); + usb_uncallout_drain(xfer->timeout_handle, uhci_timeout, + &UXFER(xfer)->iinfo); + xfer->timeout_active = 0; + } + splx(s); + if (sc->sc_dying) { /* If we're dying, just do the software part. */ s = splusb(); - xfer->status = status; /* make software ignore it */ - usb_uncallout(xfer->timeout_handle, uhci_timeout, xfer); usb_transfer_complete(xfer); - usb_rem_task(xfer->pipe->device, &UXFER(xfer)->abort_task); splx(s); return; } @@ -1955,18 +1997,6 @@ panic("uhci_abort_xfer: not in process context"); /* - * Step 1: Make interrupt routine and hardware ignore xfer. - */ - s = splusb(); - xfer->status = status; /* make software ignore it */ - usb_uncallout(xfer->timeout_handle, uhci_timeout, ii); - usb_rem_task(xfer->pipe->device, &UXFER(xfer)->abort_task); - DPRINTFN(1,("uhci_abort_xfer: stop ii=%p\n", ii)); - for (std = ii->stdstart; std != NULL; std = std->link.std) - std->td.td_status &= htole32(~(UHCI_TD_ACTIVE | UHCI_TD_IOC)); - splx(s); - - /* * Step 2: Wait until we know hardware has finished any possible * use of the xfer. Also make sure the soft interrupt routine * has run. @@ -2319,6 +2349,7 @@ if (xfer->timeout && !sc->sc_bus.use_polling) { usb_callout(xfer->timeout_handle, MS_TO_TICKS(xfer->timeout), uhci_timeout, ii); + xfer->timeout_active = 1; } xfer->status = USBD_IN_PROGRESS; splx(s); Index: usbdivar.h =================================================================== RCS file: /home/ncvs/src/sys/dev/usb/usbdivar.h,v retrieving revision 1.42 diff -u -r1.42 usbdivar.h --- usbdivar.h 3 Nov 2004 01:52:50 -0000 1.42 +++ usbdivar.h 12 Nov 2004 03:26:31 -0000 @@ -225,6 +225,7 @@ void *hcpriv; /* private use by the HC driver */ usb_callout_t timeout_handle; + int timeout_active; }; void usbd_init(void);