Re: Unloading USB driver while device is attached.

From: Craig Rodrigues <rodrigc_at_crodrigues.org>
Date: Mon, 19 Jul 2004 23:19:18 -0400
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
_at__at_ -85,6 +85,10 _at__at_
 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;
 
_at__at_ -281,7 +285,7 _at__at_
 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;
_at__at_ -300,43 +304,36 _at__at_
 	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);
_at__at_ -345,6 +342,51 _at__at_
 	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)
_at__at_ -353,42 +395,103 _at__at_
 	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
_at__at_ -746,6 +849,7 _at__at_
 			    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;
_at__at_ -757,12 +861,14 _at__at_
 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;
 
_at__at_ -787,23 +893,14 _at__at_
 	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;
 		}
 	}
_at__at_ -833,28 +930,32 _at__at_
 		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
_at__at_ -875,6 +976,7 _at__at_
 	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
Received on Tue Jul 20 2004 - 01:19:30 UTC

This archive was generated by hypermail 2.4.0 : Wed May 19 2021 - 11:38:02 UTC