diff mbox series

Partial isochronous support for dummy-hcd [was: Re: Stalling non-0-length OUT control requests in Raw Gadget/GadgetFS]

Message ID 82b983a3-7ed5-4800-8a3d-1615c381d115@rowland.harvard.edu
State New
Headers show
Series Partial isochronous support for dummy-hcd [was: Re: Stalling non-0-length OUT control requests in Raw Gadget/GadgetFS] | expand

Commit Message

Alan Stern June 17, 2024, 3:02 p.m. UTC
On Mon, Jun 17, 2024 at 08:29:12AM +0200, Greg KH wrote:
> On Sun, Jun 16, 2024 at 10:10:33PM -0400, Alan Stern wrote:
> > However, it's another thing I have lost track of.  It may have been 
> > posted to linux-usb at one point, but that would have been quite some 
> > time ago.  Probably before well lore.kernel.org existed.  (I vaguely 
> > remember working on it before moving to my current home, which means 
> > in 2008 or earlier.)
> 
> I think lore is populated with all of the linux-usb mailing list
> archives, but we might have missed from before when there was -user and
> -devel versions of the list.
> 
> If needed, I can try to dig up my older archives from cold storage and
> get Konstantin to import them.

No need -- I found a copy of it on an old backup drive.  It probably got 
left behind when I switched my system over from using 32-bit userspace 
to 64 bits.

For the record, the patch is appended below.  The file on the backup 
drive was dated May 2006 (before the 2.6.17 kernel was released!); the 
original work may have been even earlier.

Alan Stern
diff mbox series

Patch

Index: usb/drivers/usb/gadget/dummy_hcd.c
===================================================================
--- usb.orig/drivers/usb/gadget/dummy_hcd.c
+++ usb/drivers/usb/gadget/dummy_hcd.c
@@ -84,6 +84,9 @@  struct dummy_ep {
 	struct usb_gadget		*gadget;
 	const struct usb_endpoint_descriptor *desc;
 	struct usb_ep			ep;
+	int				iso_desc_num;
+	int				iso_status;
+	int				interval_left;
 	unsigned			halted : 1;
 	unsigned			already_seen : 1;
 	unsigned			setup_stage : 1;
@@ -808,6 +811,7 @@  usb_gadget_register_driver (struct usb_g
 
 	dum->gadget.ep0 = &dum->ep [0].ep;
 	dum->ep [0].ep.maxpacket = 64;
+	dum->ep [0].setup_stage = 1;
 	list_del_init (&dum->ep [0].ep.ep_list);
 	INIT_LIST_HEAD(&dum->fifo_req.queue);
 
@@ -1017,8 +1021,7 @@  static int dummy_urb_enqueue (
 
 	list_add_tail (&urbp->urbp_list, &dum->urbp_list);
 	urb->hcpriv = urbp;
-	if (usb_pipetype (urb->pipe) == PIPE_CONTROL)
-		urb->error_count = 1;		/* mark as a new urb */
+	urb->error_count = 0;
 
 	/* kick the scheduler, it'll do the rest */
 	if (!timer_pending (&dum->timer))
@@ -1056,13 +1059,44 @@  static int
 transfer (struct dummy *dum, struct urb *urb, struct dummy_ep *ep, int limit)
 {
 	struct dummy_request	*req;
+	struct usb_iso_packet_descriptor *iso;
+	int			to_host;
+
+	to_host = usb_pipein (urb->pipe);
+	iso = NULL;
+	if (usb_pipetype (urb->pipe) == PIPE_ISOCHRONOUS)
+		iso = &urb->iso_frame_desc [ep->iso_desc_num];
 
 top:
+	/* isochronous transfers go through even without a responder */
+	if (iso && list_empty (&ep->queue)) {
+		int		limit2 = limit;
+		int		maxlen = ep->ep.maxpacket;
+
+		while (maxlen <= limit2) {
+			limit2 -= maxlen;
+			if (to_host)
+				ep->iso_status = iso->status = -EPROTO;
+			else {
+				iso->status = 0;
+				iso->actual_length = iso->length;
+				urb->actual_length += iso->length;
+			}
+			++iso;
+			if (++ep->iso_desc_num >= urb->number_of_packets) {
+				maybe_set_status (urb, ep->iso_status);
+				break;
+			}
+		}
+		return limit;
+	}
+
 	/* if there's no request queued, the device is NAKing; return */
 	list_for_each_entry (req, &ep->queue, queue) {
 		unsigned	host_len, dev_len, len;
-		int		is_short, to_host;
+		int		is_short;
 		int		rescan = 0;
+		int		ustatus;
 
 		/* 1..N packets of ep->ep.maxpacket each ... the last one
 		 * may be short (including zero length).
@@ -1071,24 +1105,22 @@  top:
 		 * (length mod maxpacket zero, and 'zero' flag); they always
 		 * terminate reads.
 		 */
-		host_len = urb->transfer_buffer_length - urb->actual_length;
+		host_len = (iso ? iso->length : urb->transfer_buffer_length -
+				urb->actual_length);
 		dev_len = req->req.length - req->req.actual;
 		len = min (host_len, dev_len);
 
 		/* FIXME update emulated data toggle too */
 
-		to_host = usb_pipein (urb->pipe);
 		if (unlikely (len == 0))
 			is_short = 1;
 		else {
 			char		*ubuf, *rbuf;
 
 			/* not enough bandwidth left? */
-			if (limit < ep->ep.maxpacket && limit < len)
+			if (limit < ep->ep.maxpacket && limit < host_len)
 				break;
 			len = min (len, (unsigned) limit);
-			if (len == 0)
-				break;
 
 			/* use an extra pass for the final short packet */
 			if (len > ep->ep.maxpacket) {
@@ -1098,15 +1130,15 @@  top:
 			is_short = (len % ep->ep.maxpacket) != 0;
 
 			/* else transfer packet(s) */
-			ubuf = urb->transfer_buffer + urb->actual_length;
+			ubuf = urb->transfer_buffer + (iso ?
+					iso->offset : urb->actual_length);
 			rbuf = req->req.buf + req->req.actual;
 			if (to_host)
 				memcpy (ubuf, rbuf, len);
 			else
 				memcpy (rbuf, ubuf, len);
-			ep->last_io = jiffies;
 
-			limit -= len;
+			limit -= (iso ? ep->ep.maxpacket : len);
 			urb->actual_length += len;
 			req->req.actual += len;
 		}
@@ -1119,21 +1151,18 @@  top:
 		 * need to emulate such data-in-flight.  so we only show part
 		 * of the URB_SHORT_NOT_OK effect: completion status.
 		 */
+		ustatus = 0;
 		if (is_short) {
 			if (host_len == dev_len) {
 				req->req.status = 0;
-				maybe_set_status (urb, 0);
 			} else if (to_host) {
 				req->req.status = 0;
 				if (dev_len > host_len)
-					maybe_set_status (urb, -EOVERFLOW);
-				else
-					maybe_set_status (urb,
-						(urb->transfer_flags
-							& URB_SHORT_NOT_OK)
-						? -EREMOTEIO : 0);
-			} else if (!to_host) {
-				maybe_set_status (urb, 0);
+					ustatus = -EOVERFLOW;
+				else if (urb->transfer_flags &
+						URB_SHORT_NOT_OK)
+					ustatus = -EREMOTEIO;
+			} else {
 				if (host_len > dev_len)
 					req->req.status = -EOVERFLOW;
 				else
@@ -1147,10 +1176,25 @@  top:
 				req->req.status = 0;
 			if (urb->transfer_buffer_length == urb->actual_length
 					&& !(urb->transfer_flags
-						& URB_ZERO_PACKET)) {
-				maybe_set_status (urb, 0);
+						& URB_ZERO_PACKET))
+				;		/* URB is done */
+			else if (!iso)
+				ustatus = 1;	/* URB continues */
+		}
+
+		if (iso) {
+			iso->actual_length = len;
+			iso->status = ustatus;
+			if (ustatus) {
+				ep->iso_status = ustatus;
+				++urb->error_count;
 			}
+			++iso;
+			if (++ep->iso_desc_num >= urb->number_of_packets)
+				maybe_set_status (urb, ep->iso_status);
 		}
+		else if (ustatus <= 0)
+			maybe_set_status (urb, ustatus);
 
 		/* device side completion --> continuable */
 		if (req->req.status != -EINPROGRESS) {
@@ -1309,10 +1353,6 @@  restart:
 		if (ep->already_seen)
 			continue;
 		ep->already_seen = 1;
-		if (ep == &dum->ep [0] && urb->error_count) {
-			ep->setup_stage = 1;	/* a new urb */
-			urb->error_count = 0;
-		}
 		if (ep->halted && !ep->setup_stage) {
 			/* NOTE: must not be iso! */
 			dev_dbg (dummy_dev(dum), "ep %s halted, urb %p\n",
@@ -1323,7 +1363,7 @@  restart:
 		/* FIXME make sure both ends agree on maxpacket */
 
 		/* handle control requests */
-		if (ep == &dum->ep [0] && ep->setup_stage) {
+		if (ep->setup_stage) {
 			struct usb_ctrlrequest		setup;
 			int				value = 1;
 			struct dummy_ep			*ep2;
@@ -1483,7 +1523,7 @@  restart:
 				if (value >= 0) {
 					/* no delays (max 64KB data stage) */
 					limit = 64*1024;
-					goto treat_control_like_bulk;
+					goto treat_like_bulk;
 				}
 				/* error, see below */
 			}
@@ -1504,25 +1544,24 @@  restart:
 		limit = total;
 		switch (usb_pipetype (urb->pipe)) {
 		case PIPE_ISOCHRONOUS:
-			/* FIXME is it urb->interval since the last xfer?
-			 * use urb->iso_frame_desc[i].
-			 * complete whether or not ep has requests queued.
-			 * report random errors, to debug drivers.
+			/* FIXME: report random errors, to debug drivers.
 			 */
+			if (--ep->interval_left > 0)
+				break;
+			ep->interval_left = urb->interval;
 			limit = max (limit, periodic_bytes (dum, ep));
-			maybe_set_status (urb, -ENOSYS);
-			break;
+			goto treat_like_bulk;
 
 		case PIPE_INTERRUPT:
-			/* FIXME is it urb->interval since the last xfer?
-			 * this almost certainly polls too fast.
-			 */
+			if (--ep->interval_left > 0)
+				break;
+			ep->interval_left = urb->interval;
 			limit = max (limit, periodic_bytes (dum, ep));
 			/* FALLTHROUGH */
 
 		// case PIPE_BULK:  case PIPE_CONTROL:
 		default:
-		treat_control_like_bulk:
+		treat_like_bulk:
 			ep->last_io = jiffies;
 			total = transfer (dum, urb, ep, limit);
 			break;
@@ -1536,8 +1575,13 @@  return_urb:
 		urb->hcpriv = NULL;
 		list_del (&urbp->urbp_list);
 		kfree (urbp);
-		if (ep)
-			ep->already_seen = ep->setup_stage = 0;
+		if (ep) {
+			ep->already_seen = 0;
+			ep->iso_desc_num = 0;
+			ep->iso_status = 0;
+			if (ep == &dum->ep [0])
+				ep->setup_stage = 1;
+		}
 
 		spin_unlock (&dum->lock);
 		usb_hcd_giveback_urb (dummy_to_hcd(dum), urb, NULL);