Fix a race condition in xfer abort. Derived from a FreeBSD patch.

An xfer could be aborted twice (which means that the second abort might
access deallocated memory).  This happened when an xfer timed out and
the timeout started an abort.  While that abort was taking place the
xfer could be cancelled (usually by closing the pipe), causing a second
abort to begin.
This is now handled by having flags indicating the abort state of an xfer.

Hopefully this will fix the occasional crashes when printing.
This commit is contained in:
augustss 2005-04-30 14:38:40 +00:00
parent dc9630d5b3
commit 5b3acf742e
4 changed files with 87 additions and 8 deletions

View File

@ -1,4 +1,4 @@
/* $NetBSD: ehci.c,v 1.95 2005/04/27 23:39:54 augustss Exp $ */
/* $NetBSD: ehci.c,v 1.96 2005/04/30 14:38:40 augustss Exp $ */
/*
* Copyright (c) 2004 The NetBSD Foundation, Inc.
@ -65,7 +65,7 @@
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: ehci.c,v 1.95 2005/04/27 23:39:54 augustss Exp $");
__KERNEL_RCSID(0, "$NetBSD: ehci.c,v 1.96 2005/04/30 14:38:40 augustss Exp $");
#include "ohci.h"
#include "uhci.h"
@ -2443,6 +2443,7 @@ ehci_abort_xfer(usbd_xfer_handle xfer, usbd_status status)
u_int32_t qhstatus;
int s;
int hit;
int wake;
DPRINTF(("ehci_abort_xfer: xfer=%p pipe=%p\n", xfer, epipe));
@ -2459,6 +2460,26 @@ ehci_abort_xfer(usbd_xfer_handle xfer, usbd_status status)
if (xfer->device->bus->intr_context || !curproc)
panic("ehci_abort_xfer: not in process context");
/*
* If an abort is already in progress then just wait for it to
* complete and return.
*/
if (xfer->hcflags & UXFER_ABORTING) {
DPRINTFN(2, ("ehci_abort_xfer: already aborting\n"));
#ifdef DIAGNOSTIC
if (status == USBD_TIMEOUT)
printf("ehci_abort_xfer: TIMEOUT while aborting\n");
#endif
/* Override the status which might be USBD_TIMEOUT. */
xfer->status = status;
DPRINTFN(2, ("ehci_abort_xfer: waiting for abort to finish\n"));
xfer->hcflags |= UXFER_ABORTWAIT;
while (xfer->hcflags & UXFER_ABORTING)
tsleep(&xfer->hcflags, PZERO, "ehciaw", 0);
return;
}
xfer->hcflags |= UXFER_ABORTING;
/*
* Step 1: Make interrupt routine and hardware ignore xfer.
*/
@ -2521,7 +2542,11 @@ ehci_abort_xfer(usbd_xfer_handle xfer, usbd_status status)
#ifdef DIAGNOSTIC
exfer->isdone = 1;
#endif
wake = xfer->hcflags & UXFER_ABORTWAIT;
xfer->hcflags &= ~(UXFER_ABORTING | UXFER_ABORTWAIT);
usb_transfer_complete(xfer);
if (wake)
wakeup(&xfer->hcflags);
splx(s);
#undef exfer

View File

@ -1,4 +1,4 @@
/* $NetBSD: ohci.c,v 1.158 2005/04/17 14:46:49 toshii Exp $ */
/* $NetBSD: ohci.c,v 1.159 2005/04/30 14:38:40 augustss Exp $ */
/* $FreeBSD: src/sys/dev/usb/ohci.c,v 1.22 1999/11/17 22:33:40 n_hibma Exp $ */
/*
@ -48,7 +48,7 @@
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: ohci.c,v 1.158 2005/04/17 14:46:49 toshii Exp $");
__KERNEL_RCSID(0, "$NetBSD: ohci.c,v 1.159 2005/04/30 14:38:40 augustss Exp $");
#include <sys/param.h>
#include <sys/systm.h>
@ -2146,6 +2146,7 @@ ohci_abort_xfer(usbd_xfer_handle xfer, usbd_status status)
ohci_soft_td_t *p, *n;
ohci_physaddr_t headp;
int s, hit;
int wake;
DPRINTF(("ohci_abort_xfer: xfer=%p pipe=%p sed=%p\n", xfer, opipe,sed));
@ -2161,6 +2162,26 @@ ohci_abort_xfer(usbd_xfer_handle xfer, usbd_status status)
if (xfer->device->bus->intr_context || !curproc)
panic("ohci_abort_xfer: not in process context");
/*
* If an abort is already in progress then just wait for it to
* complete and return.
*/
if (xfer->hcflags & UXFER_ABORTING) {
DPRINTFN(2, ("ohci_abort_xfer: already aborting\n"));
#ifdef DIAGNOSTIC
if (status == USBD_TIMEOUT)
printf("0hci_abort_xfer: TIMEOUT while aborting\n");
#endif
/* Override the status which might be USBD_TIMEOUT. */
xfer->status = status;
DPRINTFN(2, ("ohci_abort_xfer: waiting for abort to finish\n"));
xfer->hcflags |= UXFER_ABORTWAIT;
while (xfer->hcflags & UXFER_ABORTING)
tsleep(&xfer->hcflags, PZERO, "ohciaw", 0);
return;
}
xfer->hcflags |= UXFER_ABORTING;
/*
* Step 1: Make interrupt routine and hardware ignore xfer.
*/
@ -2198,6 +2219,7 @@ ohci_abort_xfer(usbd_xfer_handle xfer, usbd_status status)
p = xfer->hcpriv;
#ifdef DIAGNOSTIC
if (p == NULL) {
xfer->hcflags &= ~UXFER_ABORTING; /* XXX */
splx(s);
printf("ohci_abort_xfer: hcpriv is NULL\n");
return;
@ -2234,7 +2256,11 @@ ohci_abort_xfer(usbd_xfer_handle xfer, usbd_status status)
/*
* Step 5: Execute callback.
*/
wake = xfer->hcflags & UXFER_ABORTWAIT;
xfer->hcflags &= ~(UXFER_ABORTING | UXFER_ABORTWAIT);
usb_transfer_complete(xfer);
if (wake)
wakeup(&xfer->hcflags);
splx(s);
}

View File

@ -1,4 +1,4 @@
/* $NetBSD: uhci.c,v 1.187 2005/04/29 19:15:13 skrll Exp $ */
/* $NetBSD: uhci.c,v 1.188 2005/04/30 14:38:40 augustss Exp $ */
/* $FreeBSD: src/sys/dev/usb/uhci.c,v 1.33 1999/11/17 22:33:41 n_hibma Exp $ */
/*
@ -49,7 +49,7 @@
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: uhci.c,v 1.187 2005/04/29 19:15:13 skrll Exp $");
__KERNEL_RCSID(0, "$NetBSD: uhci.c,v 1.188 2005/04/30 14:38:40 augustss Exp $");
#include <sys/param.h>
#include <sys/systm.h>
@ -1939,6 +1939,7 @@ uhci_abort_xfer(usbd_xfer_handle xfer, usbd_status status)
uhci_softc_t *sc = (uhci_softc_t *)upipe->pipe.device->bus;
uhci_soft_td_t *std;
int s;
int wake;
DPRINTFN(1,("uhci_abort_xfer: xfer=%p, status=%d\n", xfer, status));
@ -1954,6 +1955,26 @@ uhci_abort_xfer(usbd_xfer_handle xfer, usbd_status status)
if (xfer->device->bus->intr_context || !curproc)
panic("uhci_abort_xfer: not in process context");
/*
* If an abort is already in progress then just wait for it to
* complete and return.
*/
if (xfer->hcflags & UXFER_ABORTING) {
DPRINTFN(2, ("uhci_abort_xfer: already aborting\n"));
#ifdef DIAGNOSTIC
if (status == USBD_TIMEOUT)
printf("uhci_abort_xfer: TIMEOUT while aborting\n");
#endif
/* Override the status which might be USBD_TIMEOUT. */
xfer->status = status;
DPRINTFN(2, ("uhci_abort_xfer: waiting for abort to finish\n"));
xfer->hcflags |= UXFER_ABORTWAIT;
while (xfer->hcflags & UXFER_ABORTING)
tsleep(&xfer->hcflags, PZERO, "uhciaw", 0);
return;
}
xfer->hcflags |= UXFER_ABORTING;
/*
* Step 1: Make interrupt routine and hardware ignore xfer.
*/
@ -1990,7 +2011,11 @@ uhci_abort_xfer(usbd_xfer_handle xfer, usbd_status status)
#ifdef DIAGNOSTIC
ii->isdone = 1;
#endif
wake = xfer->hcflags & UXFER_ABORTWAIT;
xfer->hcflags &= ~(UXFER_ABORTING | UXFER_ABORTWAIT);
usb_transfer_complete(xfer);
if (wake)
wakeup(&xfer->hcflags);
splx(s);
}

View File

@ -1,4 +1,4 @@
/* $NetBSD: usbdivar.h,v 1.73 2005/01/24 01:30:38 joff Exp $ */
/* $NetBSD: usbdivar.h,v 1.74 2005/04/30 14:38:40 augustss Exp $ */
/* $FreeBSD: src/sys/dev/usb/usbdivar.h,v 1.11 1999/11/17 22:33:51 n_hibma Exp $ */
/*
@ -217,7 +217,7 @@ struct usbd_xfer {
struct usbd_device *device;
usb_dma_t dmabuf;
int rqflags;
u_int8_t rqflags;
#define URQ_REQUEST 0x01
#define URQ_AUTO_DMABUF 0x10
#define URQ_DEV_DMABUF 0x20
@ -225,6 +225,9 @@ struct usbd_xfer {
SIMPLEQ_ENTRY(usbd_xfer) next;
void *hcpriv; /* private use by the HC driver */
u_int8_t hcflags; /* private use by the HC driver */
#define UXFER_ABORTING 0x01 /* xfer is aborting. */
#define UXFER_ABORTWAIT 0x02 /* abort completion is being awaited. */
usb_callout_t timeout_handle;
};