Unloading USB driver while device is attached.
Craig Rodrigues
rodrigc at crodrigues.org
Mon Jul 19 20:19:30 PDT 2004
On Tue, Jul 20, 2004 at 12:45:08AM +0100, Ian Dowse wrote:
> In message <20040719.170132.52458790.imp at bsdimp.com>, "M. Warner Losh" writes:
> >In message: <200407191950.aa12733 at salmon.maths.tcd.ie>
> > Ian Dowse <iedowse at maths.tcd.ie> writes:
> >: http://people.freebsd.org/~iedowse/usb.diff
This patch doesn't solve my EHCI problems
( see: http://lists.freebsd.org/pipermail/freebsd-current/2004-July/031838.html )
but it seems like a step in the right direction.
For one thing, I don't panic in EHCI when I reboot my system
after using my Creative Nomad.
I had to change your patch a bit so that it would work against
version 1.86 of usbdi.c:
Index: usbdi.c
===================================================================
RCS file: /home/ncvs/src/sys/dev/usb/usbdi.c,v
retrieving revision 1.86
diff -u -r1.86 usbdi.c
--- usbdi.c 19 Jul 2004 20:49:02 -0000 1.86
+++ usbdi.c 20 Jul 2004 03:16:46 -0000
@@ -85,6 +85,10 @@
Static usbd_status usbd_open_pipe_ival
(usbd_interface_handle, u_int8_t, u_int8_t, usbd_pipe_handle *, int);
Static int usbd_xfer_isread(usbd_xfer_handle xfer);
+Static void usbd_start_transfer(void *arg, bus_dma_segment_t *segs, int nseg,
+ int error);
+Static void usbd_alloc_callback(void *arg, bus_dma_segment_t *segs, int nseg,
+ int error);
Static int usbd_nbuses = 0;
@@ -281,7 +285,7 @@
usbd_transfer(usbd_xfer_handle xfer)
{
usbd_pipe_handle pipe = xfer->pipe;
- usb_dma_t *dmap = &xfer->dmabuf;
+ struct usb_dma_mapping *dmap = &xfer->dmamap;
usbd_status err;
u_int size;
int s;
@@ -300,43 +304,36 @@
size = xfer->length;
/* If there is no buffer, allocate one. */
if (!(xfer->rqflags & URQ_DEV_DMABUF) && size != 0) {
- struct usbd_bus *bus = pipe->device->bus;
+ bus_dma_tag_t tag = pipe->device->bus->buffer_dmatag;
#ifdef DIAGNOSTIC
if (xfer->rqflags & URQ_AUTO_DMABUF)
printf("usbd_transfer: has old buffer!\n");
#endif
- err = bus->methods->allocm(bus, dmap, size);
+ err = bus_dmamap_create(tag, 0, &dmap->map);
if (err)
- return (err);
- xfer->rqflags |= URQ_AUTO_DMABUF;
- }
-
- /* Copy data if going out. */
- if (!(xfer->flags & USBD_NO_COPY) && size != 0 &&
- !usbd_xfer_isread(xfer))
- memcpy(KERNADDR(dmap, 0), xfer->buffer, size);
-
- err = pipe->methods->transfer(xfer);
+ return (USBD_NOMEM);
- if (err != USBD_IN_PROGRESS && err) {
- /* The transfer has not been queued, so free buffer. */
- if (xfer->rqflags & URQ_AUTO_DMABUF) {
- struct usbd_bus *bus = pipe->device->bus;
-
- bus->methods->freem(bus, &xfer->dmabuf);
+ xfer->rqflags |= URQ_AUTO_DMABUF;
+ err = bus_dmamap_load(tag, dmap->map, xfer->buffer, size,
+ usbd_start_transfer, xfer, 0);
+ if (err != 0 && err != EINPROGRESS) {
xfer->rqflags &= ~URQ_AUTO_DMABUF;
+ bus_dmamap_destroy(tag, dmap->map);
+ return (USBD_INVAL);
}
+ } else if (size != 0) {
+ usbd_start_transfer(xfer, dmap->segs, dmap->nsegs, 0);
+ } else {
+ usbd_start_transfer(xfer, NULL, 0, 0);
}
if (!(xfer->flags & USBD_SYNCHRONOUS))
- return (err);
+ return (xfer->done ? 0 : USBD_IN_PROGRESS);
/* Sync transfer, wait for completion. */
- if (err != USBD_IN_PROGRESS)
- return (err);
s = splusb();
- if (!xfer->done) {
+ while (!xfer->done) {
if (pipe->device->bus->use_polling)
panic("usbd_transfer: not done");
tsleep(xfer, PRIBIO, "usbsyn", 0);
@@ -345,6 +342,51 @@
return (xfer->status);
}
+Static void
+usbd_start_transfer(void *arg, bus_dma_segment_t *segs, int nseg, int error)
+{
+ usbd_xfer_handle xfer = arg;
+ usbd_pipe_handle pipe = xfer->pipe;
+ struct usb_dma_mapping *dmap = &xfer->dmamap;
+ bus_dma_tag_t tag = pipe->device->bus->buffer_dmatag;
+ int err, i;
+
+ if (error != 0) {
+ KASSERT(xfer->rqflags & URQ_AUTO_DMABUF,
+ ("usbd_start_transfer: error with non-auto buf"));
+ if (nseg > 0)
+ bus_dmamap_unload(tag, dmap->map);
+ bus_dmamap_destroy(tag, dmap->map);
+ /* XXX */
+ usb_insert_transfer(xfer);
+ xfer->status = USBD_IOERROR;
+ usb_transfer_complete(xfer);
+ return;
+ }
+
+ if (segs != dmap->segs) {
+ for (i = 0; i < nseg; i++)
+ dmap->segs[i] = segs[i];
+ dmap->nsegs = nseg;
+ }
+
+ if (segs > 0 && !usbd_xfer_isread(xfer))
+ bus_dmamap_sync(tag, dmap->map, BUS_DMASYNC_PREREAD);
+ err = pipe->methods->transfer(xfer);
+ if (err != USBD_IN_PROGRESS && err) {
+ if (xfer->rqflags & URQ_AUTO_DMABUF) {
+ bus_dmamap_unload(tag, dmap->map);
+ bus_dmamap_destroy(tag, dmap->map);
+ xfer->rqflags &= ~URQ_AUTO_DMABUF;
+ }
+ /* XXX */
+ usb_insert_transfer(xfer);
+ xfer->status = err;
+ usb_transfer_complete(xfer);
+ return;
+ }
+}
+
/* Like usbd_transfer(), but waits for completion. */
usbd_status
usbd_sync_transfer(usbd_xfer_handle xfer)
@@ -353,42 +395,103 @@
return (usbd_transfer(xfer));
}
+struct usbd_allocstate {
+ usbd_xfer_handle xfer;
+ int done;
+ int error;
+ int waiting;
+};
+
void *
usbd_alloc_buffer(usbd_xfer_handle xfer, u_int32_t size)
{
- struct usbd_bus *bus = xfer->device->bus;
+ struct usbd_allocstate allocstate;
+ struct usb_dma_mapping *dmap = &xfer->dmamap;
+ bus_dma_tag_t tag = xfer->device->bus->buffer_dmatag;
+ void *buf;
usbd_status err;
+ int error, s;
-#ifdef DIAGNOSTIC
- if (xfer->rqflags & (URQ_DEV_DMABUF | URQ_AUTO_DMABUF))
- printf("usbd_alloc_buffer: xfer already has a buffer\n");
-#endif
- err = bus->methods->allocm(bus, &xfer->dmabuf, size);
+ KASSERT((xfer->rqflags & (URQ_DEV_DMABUF | URQ_AUTO_DMABUF)) == 0,
+ ("usbd_alloc_buffer: xfer already has a buffer"));
+ err = bus_dmamap_create(tag, 0, &dmap->map);
if (err)
return (NULL);
+ buf = malloc(size, M_USB, M_WAITOK);
+
+ allocstate.xfer = xfer;
+ allocstate.done = 0;
+ allocstate.error = 0;
+ allocstate.waiting = 0;
+ error = bus_dmamap_load(tag, dmap->map, buf, size, usbd_alloc_callback,
+ &allocstate, 0);
+ if (error && error != EINPROGRESS) {
+ bus_dmamap_destroy(tag, dmap->map);
+ free(buf, M_USB);
+ return (NULL);
+ }
+ if (error == EINPROGRESS) {
+ /* Wait for completion. */
+ s = splusb();
+ allocstate.waiting = 1;
+ while (!allocstate.done)
+ tsleep(&allocstate, PRIBIO, "usbdab", 0);
+ splx(s);
+ error = allocstate.error;
+ }
+ if (error) {
+ bus_dmamap_unload(tag, dmap->map);
+ bus_dmamap_destroy(tag, dmap->map);
+ free(buf, M_USB);
+ return (NULL);
+ }
+
+ xfer->allocbuf = buf;
xfer->rqflags |= URQ_DEV_DMABUF;
- return (KERNADDR(&xfer->dmabuf, 0));
+ return (buf);
}
void
usbd_free_buffer(usbd_xfer_handle xfer)
{
-#ifdef DIAGNOSTIC
- if (!(xfer->rqflags & (URQ_DEV_DMABUF | URQ_AUTO_DMABUF))) {
- printf("usbd_free_buffer: no buffer\n");
- return;
- }
-#endif
- xfer->rqflags &= ~(URQ_DEV_DMABUF | URQ_AUTO_DMABUF);
- xfer->device->bus->methods->freem(xfer->device->bus, &xfer->dmabuf);
+ struct usb_dma_mapping *dmap = &xfer->dmamap;
+ bus_dma_tag_t tag = xfer->device->bus->buffer_dmatag;
+
+ KASSERT((xfer->rqflags & (URQ_DEV_DMABUF | URQ_AUTO_DMABUF)) ==
+ URQ_DEV_DMABUF, ("usbd_free_buffer: no/auto buffer"));
+
+ xfer->rqflags &= ~URQ_DEV_DMABUF;
+ bus_dmamap_unload(tag, dmap->map);
+ bus_dmamap_destroy(tag, dmap->map);
+ free(xfer->allocbuf, M_USB);
+ xfer->allocbuf = NULL;
}
void *
usbd_get_buffer(usbd_xfer_handle xfer)
{
if (!(xfer->rqflags & URQ_DEV_DMABUF))
- return (0);
- return (KERNADDR(&xfer->dmabuf, 0));
+ return (NULL);
+ return (xfer->allocbuf);
+}
+
+Static void
+usbd_alloc_callback(void *arg, bus_dma_segment_t *segs, int nseg, int error)
+{
+ struct usbd_allocstate *allocstate = arg;
+ usbd_xfer_handle xfer = allocstate->xfer;
+ struct usb_dma_mapping *dmap = &xfer->dmamap;
+ int i;
+
+ allocstate->error = error;
+ if (error == 0) {
+ for (i = 0; i < nseg; i++)
+ dmap->segs[i] = segs[i];
+ dmap->nsegs = nseg;
+ }
+ allocstate->done = 1;
+ if (allocstate->waiting)
+ wakeup(&allocstate);
}
usbd_xfer_handle
@@ -746,6 +849,7 @@
pipe, xfer, pipe->methods));
/* Make the HC abort it (and invoke the callback). */
pipe->methods->abort(xfer);
+ KASSERT(SIMPLEQ_FIRST(&pipe->queue) != xfer, ("usbd_ar_pipe"));
/* XXX only for non-0 usbd_clear_endpoint_stall(pipe); */
}
pipe->aborting = 0;
@@ -757,12 +861,14 @@
usb_transfer_complete(usbd_xfer_handle xfer)
{
usbd_pipe_handle pipe = xfer->pipe;
- usb_dma_t *dmap = &xfer->dmabuf;
+ struct usb_dma_mapping *dmap = &xfer->dmamap;
+ bus_dma_tag_t tag = pipe->device->bus->buffer_dmatag;
+ usbd_status status;
int sync = xfer->flags & USBD_SYNCHRONOUS;
int erred = xfer->status == USBD_CANCELLED ||
- xfer->status == USBD_TIMEOUT;
+ xfer->status == USBD_TIMEOUT;
int repeat = pipe->repeat;
- int polling;
+ int polling, xfer_flags;
SPLUSBCHECK;
@@ -787,23 +893,14 @@
if (polling)
pipe->running = 0;
- if (!(xfer->flags & USBD_NO_COPY) && xfer->actlen != 0 &&
- usbd_xfer_isread(xfer)) {
-#ifdef DIAGNOSTIC
- if (xfer->actlen > xfer->length) {
- printf("usb_transfer_complete: actlen > len %d > %d\n",
- xfer->actlen, xfer->length);
- xfer->actlen = xfer->length;
- }
-#endif
- memcpy(xfer->buffer, KERNADDR(dmap, 0), xfer->actlen);
- }
+ if (xfer->actlen != 0 && usbd_xfer_isread(xfer))
+ bus_dmamap_sync(tag, dmap->map, BUS_DMASYNC_POSTWRITE);
- /* if we allocated the buffer in usbd_transfer() we free it here. */
+ /* if we mapped the buffer in usbd_transfer() we unmap it here. */
if (xfer->rqflags & URQ_AUTO_DMABUF) {
if (!repeat) {
- struct usbd_bus *bus = pipe->device->bus;
- bus->methods->freem(bus, dmap);
+ bus_dmamap_unload(tag, dmap->map);
+ bus_dmamap_destroy(tag, dmap->map);
xfer->rqflags &= ~URQ_AUTO_DMABUF;
}
}
@@ -833,28 +930,32 @@
xfer->status = USBD_SHORT_XFER;
}
- if (xfer->callback)
- xfer->callback(xfer, xfer->priv, xfer->status);
-
-#ifdef DIAGNOSTIC
- if (pipe->methods->done != NULL)
+ /* Copy any xfer fields in case the xfer goes away in the callback. */
+ status = xfer->status;
+ xfer_flags = xfer->flags;
+ /*
+ * For repeat operations, call the callback first, as the xfer
+ * will not go away and the "done" method may modify it. Otherwise
+ * reverse the order in case the callback wants to free or reuse
+ * the xfer.
+ */
+ if (repeat) {
+ if (xfer->callback)
+ xfer->callback(xfer, xfer->priv, status);
pipe->methods->done(xfer);
- else
- printf("usb_transfer_complete: pipe->methods->done == NULL\n");
-#else
- pipe->methods->done(xfer);
-#endif
-
- if (sync && !polling)
- wakeup(xfer);
+ } else {
+ pipe->methods->done(xfer);
+ if (xfer->callback)
+ xfer->callback(xfer, xfer->priv, status);
- if (!repeat) {
/* XXX should we stop the queue on all errors? */
if (erred && pipe->iface != NULL) /* not control pipe */
pipe->running = 0;
else
usbd_start_next(pipe);
}
+ if (sync && !polling)
+ wakeup(xfer);
}
usbd_status
@@ -875,6 +976,7 @@
xfer->busy_free = XFER_ONQU;
#endif
s = splusb();
+ KASSERT(SIMPLEQ_FIRST(&pipe->queue) != xfer, ("usb_insert_transfer"));
SIMPLEQ_INSERT_TAIL(&pipe->queue, xfer, next);
if (pipe->running)
err = USBD_IN_PROGRESS;
--
Craig Rodrigues
http://crodrigues.org
rodrigc at crodrigues.org
More information about the freebsd-current
mailing list