git: f83f5d58394d - main - Send a zero-length-packet first when opening a BULK endpoint for USB serial port devices. If it gets eaten it is fine. Many USB device side implementations don't properly support the clear endpoint halt command and if they do, data is lost because the transmit FIFO is typically reset when this command is received.

Hans Petter Selasky hselasky at FreeBSD.org
Thu Jul 15 10:13:29 UTC 2021


The branch main has been updated by hselasky:

URL: https://cgit.FreeBSD.org/src/commit/?id=f83f5d58394db57576bbed6dc7531997cabeb102

commit f83f5d58394db57576bbed6dc7531997cabeb102
Author:     Hans Petter Selasky <hselasky at FreeBSD.org>
AuthorDate: 2021-07-06 10:29:57 +0000
Commit:     Hans Petter Selasky <hselasky at FreeBSD.org>
CommitDate: 2021-07-15 10:11:53 +0000

    Send a zero-length-packet first when opening a BULK endpoint for USB serial
    port devices. If it gets eaten it is fine. Many USB device side implementations
    don't properly support the clear endpoint halt command and if they do, data is lost
    because the transmit FIFO is typically reset when this command is received.
    
    Tested by:      jmg
    MFC after:      1 week
    Sponsored by:   NVIDIA Networking
---
 sys/dev/usb/serial/u3g.c     |  6 +++--
 sys/dev/usb/serial/uark.c    | 10 +++++---
 sys/dev/usb/serial/ubsa.c    | 10 +++++---
 sys/dev/usb/serial/ubser.c   | 10 +++++---
 sys/dev/usb/serial/uchcom.c  |  6 +++--
 sys/dev/usb/serial/ufoma.c   | 10 +++++---
 sys/dev/usb/serial/uftdi.c   |  6 +++--
 sys/dev/usb/serial/ugensa.c  | 11 +++++----
 sys/dev/usb/serial/uipaq.c   | 10 +++++---
 sys/dev/usb/serial/ulpt.c    |  9 +++----
 sys/dev/usb/serial/umcs.c    | 10 +++++---
 sys/dev/usb/serial/umodem.c  | 18 +++++++-------
 sys/dev/usb/serial/umoscom.c | 10 +++++---
 sys/dev/usb/serial/uplcom.c  | 21 ++++++++--------
 sys/dev/usb/serial/uslcom.c  | 10 +++++---
 sys/dev/usb/serial/uvscom.c  | 10 +++++---
 sys/dev/usb/usb_transfer.c   | 59 +++++++++++++++++++++++++++++++++++++++++---
 sys/dev/usb/usbdi.h          |  3 +++
 18 files changed, 155 insertions(+), 74 deletions(-)

diff --git a/sys/dev/usb/serial/u3g.c b/sys/dev/usb/serial/u3g.c
index 22e4e8f6b2bb..d7d8fd80b42a 100644
--- a/sys/dev/usb/serial/u3g.c
+++ b/sys/dev/usb/serial/u3g.c
@@ -978,8 +978,7 @@ u3g_attach(device_t dev)
 
 		/* set stall by default */
 		mtx_lock(&sc->sc_mtx);
-		usbd_xfer_set_stall(sc->sc_xfer[nports][U3G_BULK_WR]);
-		usbd_xfer_set_stall(sc->sc_xfer[nports][U3G_BULK_RD]);
+		usbd_xfer_set_zlp(sc->sc_xfer[nports][U3G_BULK_WR]);
 		mtx_unlock(&sc->sc_mtx);
 
 		nports++;	/* found one port */
@@ -1100,6 +1099,9 @@ u3g_write_callback(struct usb_xfer *xfer, usb_error_t error)
 	case USB_ST_TRANSFERRED:
 	case USB_ST_SETUP:
 tr_setup:
+		if (usbd_xfer_get_and_clr_zlp(xfer))
+			break;
+
 		for (frame = 0; frame != U3G_TXFRAMES; frame++) {
 			usbd_xfer_set_frame_offset(xfer, frame * U3G_TXSIZE, frame);
 
diff --git a/sys/dev/usb/serial/uark.c b/sys/dev/usb/serial/uark.c
index 904977e1ec14..072edf6389cd 100644
--- a/sys/dev/usb/serial/uark.c
+++ b/sys/dev/usb/serial/uark.c
@@ -219,8 +219,7 @@ uark_attach(device_t dev)
 	}
 	/* clear stall at first run */
 	mtx_lock(&sc->sc_mtx);
-	usbd_xfer_set_stall(sc->sc_xfer[UARK_BULK_DT_WR]);
-	usbd_xfer_set_stall(sc->sc_xfer[UARK_BULK_DT_RD]);
+	usbd_xfer_set_zlp(sc->sc_xfer[UARK_BULK_DT_WR]);
 	mtx_unlock(&sc->sc_mtx);
 
 	error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc,
@@ -281,13 +280,16 @@ uark_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error)
 	case USB_ST_SETUP:
 	case USB_ST_TRANSFERRED:
 tr_setup:
+		if (usbd_xfer_get_and_clr_zlp(xfer))
+			break;
+
 		pc = usbd_xfer_get_frame(xfer, 0);
 		if (ucom_get_data(&sc->sc_ucom, pc, 0,
 		    UARK_BUF_SIZE, &actlen)) {
 			usbd_xfer_set_frame_len(xfer, 0, actlen);
 			usbd_transfer_submit(xfer);
 		}
-		return;
+		break;
 
 	default:			/* Error */
 		if (error != USB_ERR_CANCELLED) {
@@ -295,7 +297,7 @@ tr_setup:
 			usbd_xfer_set_stall(xfer);
 			goto tr_setup;
 		}
-		return;
+		break;
 	}
 }
 
diff --git a/sys/dev/usb/serial/ubsa.c b/sys/dev/usb/serial/ubsa.c
index 10b37984db2f..3286df7331b4 100644
--- a/sys/dev/usb/serial/ubsa.c
+++ b/sys/dev/usb/serial/ubsa.c
@@ -320,8 +320,7 @@ ubsa_attach(device_t dev)
 	}
 	/* clear stall at first run */
 	mtx_lock(&sc->sc_mtx);
-	usbd_xfer_set_stall(sc->sc_xfer[UBSA_BULK_DT_WR]);
-	usbd_xfer_set_stall(sc->sc_xfer[UBSA_BULK_DT_RD]);
+	usbd_xfer_set_zlp(sc->sc_xfer[UBSA_BULK_DT_WR]);
 	mtx_unlock(&sc->sc_mtx);
 
 	error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc,
@@ -583,13 +582,16 @@ ubsa_write_callback(struct usb_xfer *xfer, usb_error_t error)
 	case USB_ST_SETUP:
 	case USB_ST_TRANSFERRED:
 tr_setup:
+		if (usbd_xfer_get_and_clr_zlp(xfer))
+			break;
+
 		pc = usbd_xfer_get_frame(xfer, 0);
 		if (ucom_get_data(&sc->sc_ucom, pc, 0,
 		    UBSA_BSIZE, &actlen)) {
 			usbd_xfer_set_frame_len(xfer, 0, actlen);
 			usbd_transfer_submit(xfer);
 		}
-		return;
+		break;
 
 	default:			/* Error */
 		if (error != USB_ERR_CANCELLED) {
@@ -597,7 +599,7 @@ tr_setup:
 			usbd_xfer_set_stall(xfer);
 			goto tr_setup;
 		}
-		return;
+		break;
 	}
 }
 
diff --git a/sys/dev/usb/serial/ubser.c b/sys/dev/usb/serial/ubser.c
index 2a35756b0a7e..61b9c6c433fb 100644
--- a/sys/dev/usb/serial/ubser.c
+++ b/sys/dev/usb/serial/ubser.c
@@ -293,8 +293,7 @@ ubser_attach(device_t dev)
 	ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev);
 
 	mtx_lock(&sc->sc_mtx);
-	usbd_xfer_set_stall(sc->sc_xfer[UBSER_BULK_DT_WR]);
-	usbd_xfer_set_stall(sc->sc_xfer[UBSER_BULK_DT_RD]);
+	usbd_xfer_set_zlp(sc->sc_xfer[UBSER_BULK_DT_WR]);
 	usbd_transfer_start(sc->sc_xfer[UBSER_BULK_DT_RD]);
 	mtx_unlock(&sc->sc_mtx);
 
@@ -410,6 +409,9 @@ ubser_write_callback(struct usb_xfer *xfer, usb_error_t error)
 	case USB_ST_SETUP:
 	case USB_ST_TRANSFERRED:
 tr_setup:
+		if (usbd_xfer_get_and_clr_zlp(xfer))
+			break;
+
 		pc = usbd_xfer_get_frame(xfer, 0);
 		do {
 			if (ucom_get_data(sc->sc_ucom + sc->sc_curr_tx_unit,
@@ -430,7 +432,7 @@ tr_setup:
 
 		} while (sc->sc_curr_tx_unit != first_unit);
 
-		return;
+		break;
 
 	default:			/* Error */
 		if (error != USB_ERR_CANCELLED) {
@@ -438,7 +440,7 @@ tr_setup:
 			usbd_xfer_set_stall(xfer);
 			goto tr_setup;
 		}
-		return;
+		break;
 	}
 }
 
diff --git a/sys/dev/usb/serial/uchcom.c b/sys/dev/usb/serial/uchcom.c
index 6b17141c6ece..aef1515d3586 100644
--- a/sys/dev/usb/serial/uchcom.c
+++ b/sys/dev/usb/serial/uchcom.c
@@ -350,8 +350,7 @@ uchcom_attach(device_t dev)
 
 	/* clear stall at first run */
 	mtx_lock(&sc->sc_mtx);
-	usbd_xfer_set_stall(sc->sc_xfer[UCHCOM_BULK_DT_WR]);
-	usbd_xfer_set_stall(sc->sc_xfer[UCHCOM_BULK_DT_RD]);
+	usbd_xfer_set_zlp(sc->sc_xfer[UCHCOM_BULK_DT_WR]);
 	mtx_unlock(&sc->sc_mtx);
 
 	error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc,
@@ -828,6 +827,9 @@ uchcom_write_callback(struct usb_xfer *xfer, usb_error_t error)
 	case USB_ST_SETUP:
 	case USB_ST_TRANSFERRED:
 tr_setup:
+		if (usbd_xfer_get_and_clr_zlp(xfer))
+			break;
+
 		pc = usbd_xfer_get_frame(xfer, 0);
 		if (ucom_get_data(&sc->sc_ucom, pc, 0,
 		    usbd_xfer_max_len(xfer), &actlen)) {
diff --git a/sys/dev/usb/serial/ufoma.c b/sys/dev/usb/serial/ufoma.c
index bc3f00a7b473..ca52752accad 100644
--- a/sys/dev/usb/serial/ufoma.c
+++ b/sys/dev/usb/serial/ufoma.c
@@ -438,8 +438,7 @@ ufoma_attach(device_t dev)
 
 	/* clear stall at first run, if any */
 	mtx_lock(&sc->sc_mtx);
-	usbd_xfer_set_stall(sc->sc_bulk_xfer[UFOMA_BULK_ENDPT_WRITE]);
-	usbd_xfer_set_stall(sc->sc_bulk_xfer[UFOMA_BULK_ENDPT_READ]);
+	usbd_xfer_set_zlp(sc->sc_bulk_xfer[UFOMA_BULK_ENDPT_WRITE]);
 	mtx_unlock(&sc->sc_mtx);
 
 	error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc,
@@ -791,13 +790,16 @@ ufoma_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error)
 	case USB_ST_SETUP:
 	case USB_ST_TRANSFERRED:
 tr_setup:
+		if (usbd_xfer_get_and_clr_zlp(xfer))
+			break;
+
 		pc = usbd_xfer_get_frame(xfer, 0);
 		if (ucom_get_data(&sc->sc_ucom, pc, 0,
 		    UFOMA_BULK_BUF_SIZE, &actlen)) {
 			usbd_xfer_set_frame_len(xfer, 0, actlen);
 			usbd_transfer_submit(xfer);
 		}
-		return;
+		break;
 
 	default:			/* Error */
 		if (error != USB_ERR_CANCELLED) {
@@ -805,7 +807,7 @@ tr_setup:
 			usbd_xfer_set_stall(xfer);
 			goto tr_setup;
 		}
-		return;
+		break;
 	}
 }
 
diff --git a/sys/dev/usb/serial/uftdi.c b/sys/dev/usb/serial/uftdi.c
index 52977352fb94..9dc00e82253f 100644
--- a/sys/dev/usb/serial/uftdi.c
+++ b/sys/dev/usb/serial/uftdi.c
@@ -1117,8 +1117,7 @@ uftdi_attach(device_t dev)
 	}
 	/* clear stall at first run */
 	mtx_lock(&sc->sc_mtx);
-	usbd_xfer_set_stall(sc->sc_xfer[UFTDI_BULK_DT_WR]);
-	usbd_xfer_set_stall(sc->sc_xfer[UFTDI_BULK_DT_RD]);
+	usbd_xfer_set_zlp(sc->sc_xfer[UFTDI_BULK_DT_WR]);
 	mtx_unlock(&sc->sc_mtx);
 
 	/* set a valid "lcr" value */
@@ -1221,6 +1220,9 @@ uftdi_write_callback(struct usb_xfer *xfer, usb_error_t error)
 		/* FALLTHROUGH */
 	case USB_ST_SETUP:
 	case USB_ST_TRANSFERRED:
+		if (usbd_xfer_get_and_clr_zlp(xfer))
+			break;
+
 		/*
 		 * If output packets don't require headers (the common case) we
 		 * can just load the buffer up with payload bytes all at once.
diff --git a/sys/dev/usb/serial/ugensa.c b/sys/dev/usb/serial/ugensa.c
index ba694e4805bd..1d2f1e57ef58 100644
--- a/sys/dev/usb/serial/ugensa.c
+++ b/sys/dev/usb/serial/ugensa.c
@@ -233,10 +233,8 @@ ugensa_attach(device_t dev)
 				break;
 			}
 
-			/* clear stall at first run */
 			mtx_lock(&sc->sc_mtx);
-			usbd_xfer_set_stall(ssc->sc_xfer[UGENSA_BULK_DT_WR]);
-			usbd_xfer_set_stall(ssc->sc_xfer[UGENSA_BULK_DT_RD]);
+			usbd_xfer_set_zlp(ssc->sc_xfer[UGENSA_BULK_DT_WR]);
 			mtx_unlock(&sc->sc_mtx);
 
 			/* initialize port number */
@@ -313,13 +311,16 @@ ugensa_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error)
 	case USB_ST_SETUP:
 	case USB_ST_TRANSFERRED:
 tr_setup:
+		if (usbd_xfer_get_and_clr_zlp(xfer))
+			break;
+
 		pc = usbd_xfer_get_frame(xfer, 0);
 		if (ucom_get_data(ssc->sc_ucom_ptr, pc, 0,
 		    UGENSA_BUF_SIZE, &actlen)) {
 			usbd_xfer_set_frame_len(xfer, 0, actlen);
 			usbd_transfer_submit(xfer);
 		}
-		return;
+		break;
 
 	default:			/* Error */
 		if (error != USB_ERR_CANCELLED) {
@@ -327,7 +328,7 @@ tr_setup:
 			usbd_xfer_set_stall(xfer);
 			goto tr_setup;
 		}
-		return;
+		break;
 	}
 }
 
diff --git a/sys/dev/usb/serial/uipaq.c b/sys/dev/usb/serial/uipaq.c
index 33f2ae4c0845..0449a5663c71 100644
--- a/sys/dev/usb/serial/uipaq.c
+++ b/sys/dev/usb/serial/uipaq.c
@@ -1157,8 +1157,7 @@ uipaq_attach(device_t dev)
 	}
 	/* clear stall at first run */
 	mtx_lock(&sc->sc_mtx);
-	usbd_xfer_set_stall(sc->sc_xfer[UIPAQ_BULK_DT_WR]);
-	usbd_xfer_set_stall(sc->sc_xfer[UIPAQ_BULK_DT_RD]);
+	usbd_xfer_set_zlp(sc->sc_xfer[UIPAQ_BULK_DT_WR]);
 	mtx_unlock(&sc->sc_mtx);
 
 	error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc,
@@ -1320,13 +1319,16 @@ uipaq_write_callback(struct usb_xfer *xfer, usb_error_t error)
 	case USB_ST_SETUP:
 	case USB_ST_TRANSFERRED:
 tr_setup:
+		if (usbd_xfer_get_and_clr_zlp(xfer))
+			break;
+
 		pc = usbd_xfer_get_frame(xfer, 0);
 		if (ucom_get_data(&sc->sc_ucom, pc, 0,
 		    UIPAQ_BUF_SIZE, &actlen)) {
 			usbd_xfer_set_frame_len(xfer, 0, actlen);
 			usbd_transfer_submit(xfer);
 		}
-		return;
+		break;
 
 	default:			/* Error */
 		if (error != USB_ERR_CANCELLED) {
@@ -1334,7 +1336,7 @@ tr_setup:
 			usbd_xfer_set_stall(xfer);
 			goto tr_setup;
 		}
-		return;
+		break;
 	}
 }
 
diff --git a/sys/dev/usb/serial/ulpt.c b/sys/dev/usb/serial/ulpt.c
index c566da924374..56f99519d4cd 100644
--- a/sys/dev/usb/serial/ulpt.c
+++ b/sys/dev/usb/serial/ulpt.c
@@ -218,6 +218,9 @@ ulpt_write_callback(struct usb_xfer *xfer, usb_error_t error)
 	case USB_ST_TRANSFERRED:
 	case USB_ST_SETUP:
 tr_setup:
+		if (usbd_xfer_get_and_clr_zlp(xfer))
+			break;
+
 		pc = usbd_xfer_get_frame(xfer, 0);
 		max = usbd_xfer_max_len(xfer);
 		if (usb_fifo_get_data(f, pc, 0, max, &actlen, 0)) {
@@ -436,10 +439,6 @@ unlpt_open(struct usb_fifo *fifo, int fflags)
 		return (EBUSY);
 	}
 	if (fflags & FREAD) {
-		/* clear stall first */
-		mtx_lock(&sc->sc_mtx);
-		usbd_xfer_set_stall(sc->sc_xfer[ULPT_BULK_DT_RD]);
-		mtx_unlock(&sc->sc_mtx);
 		if (usb_fifo_alloc_buffer(fifo,
 		    usbd_xfer_max_len(sc->sc_xfer[ULPT_BULK_DT_RD]),
 		    ULPT_IFQ_MAXLEN)) {
@@ -451,7 +450,7 @@ unlpt_open(struct usb_fifo *fifo, int fflags)
 	if (fflags & FWRITE) {
 		/* clear stall first */
 		mtx_lock(&sc->sc_mtx);
-		usbd_xfer_set_stall(sc->sc_xfer[ULPT_BULK_DT_WR]);
+		usbd_xfer_set_zlp(sc->sc_xfer[ULPT_BULK_DT_WR]);
 		mtx_unlock(&sc->sc_mtx);
 		if (usb_fifo_alloc_buffer(fifo,
 		    usbd_xfer_max_len(sc->sc_xfer[ULPT_BULK_DT_WR]),
diff --git a/sys/dev/usb/serial/umcs.c b/sys/dev/usb/serial/umcs.c
index e3c8f1a764fb..76ecd1ffa452 100644
--- a/sys/dev/usb/serial/umcs.c
+++ b/sys/dev/usb/serial/umcs.c
@@ -382,8 +382,7 @@ umcs7840_attach(device_t dev)
 	/* clear stall at first run */
 	mtx_lock(&sc->sc_mtx);
 	for (subunit = 0; subunit < sc->sc_numports; ++subunit) {
-		usbd_xfer_set_stall(sc->sc_ports[sc->sc_ucom[subunit].sc_portno].sc_xfer[UMCS7840_BULK_RD_EP]);
-		usbd_xfer_set_stall(sc->sc_ports[sc->sc_ucom[subunit].sc_portno].sc_xfer[UMCS7840_BULK_WR_EP]);
+		usbd_xfer_set_zlp(sc->sc_ports[sc->sc_ucom[subunit].sc_portno].sc_xfer[UMCS7840_BULK_WR_EP]);
 	}
 	mtx_unlock(&sc->sc_mtx);
 
@@ -916,13 +915,16 @@ umcs7840_write_callbackN(struct usb_xfer *xfer, usb_error_t error, uint8_t subun
 	case USB_ST_SETUP:
 	case USB_ST_TRANSFERRED:
 tr_setup:
+		if (usbd_xfer_get_and_clr_zlp(xfer))
+			break;
+
 		pc = usbd_xfer_get_frame(xfer, 0);
 		if (ucom_get_data(ucom, pc, 0, usbd_xfer_max_len(xfer), &actlen)) {
 			DPRINTF("Port %d write, has %d bytes\n", ucom->sc_portno, actlen);
 			usbd_xfer_set_frame_len(xfer, 0, actlen);
 			usbd_transfer_submit(xfer);
 		}
-		return;
+		break;
 
 	default:			/* Error */
 		if (error != USB_ERR_CANCELLED) {
@@ -930,7 +932,7 @@ tr_setup:
 			usbd_xfer_set_stall(xfer);
 			goto tr_setup;
 		}
-		return;
+		break;
 	}
 }
 
diff --git a/sys/dev/usb/serial/umodem.c b/sys/dev/usb/serial/umodem.c
index 573d74cdb526..b8b7ae2f005c 100644
--- a/sys/dev/usb/serial/umodem.c
+++ b/sys/dev/usb/serial/umodem.c
@@ -447,13 +447,10 @@ umodem_attach(device_t dev)
 		goto detach;
 	}
 
-	/* clear stall at first run, if USB host mode */
-	if (uaa->usb_mode == USB_MODE_HOST) {
-		mtx_lock(&sc->sc_mtx);
-		usbd_xfer_set_stall(sc->sc_xfer[UMODEM_BULK_WR]);
-		usbd_xfer_set_stall(sc->sc_xfer[UMODEM_BULK_RD]);
-		mtx_unlock(&sc->sc_mtx);
-	}
+	/* send a ZLP at first run */
+	mtx_lock(&sc->sc_mtx);
+	usbd_xfer_set_zlp(sc->sc_xfer[UMODEM_BULK_WR]);
+	mtx_unlock(&sc->sc_mtx);
 
 	ucom_set_usb_mode(&sc->sc_super_ucom, uaa->usb_mode);
 
@@ -863,13 +860,16 @@ umodem_write_callback(struct usb_xfer *xfer, usb_error_t error)
 	case USB_ST_SETUP:
 	case USB_ST_TRANSFERRED:
 tr_setup:
+		if (usbd_xfer_get_and_clr_zlp(xfer))
+			break;
+
 		pc = usbd_xfer_get_frame(xfer, 0);
 		if (ucom_get_data(&sc->sc_ucom, pc, 0,
 		    UMODEM_BUF_SIZE, &actlen)) {
 			usbd_xfer_set_frame_len(xfer, 0, actlen);
 			usbd_transfer_submit(xfer);
 		}
-		return;
+		break;
 
 	default:			/* Error */
 		if (error != USB_ERR_CANCELLED) {
@@ -877,7 +877,7 @@ tr_setup:
 			usbd_xfer_set_stall(xfer);
 			goto tr_setup;
 		}
-		return;
+		break;
 	}
 }
 
diff --git a/sys/dev/usb/serial/umoscom.c b/sys/dev/usb/serial/umoscom.c
index 724ab7f94092..536772a7f697 100644
--- a/sys/dev/usb/serial/umoscom.c
+++ b/sys/dev/usb/serial/umoscom.c
@@ -333,8 +333,7 @@ umoscom_attach(device_t dev)
 	}
 	/* clear stall at first run */
 	mtx_lock(&sc->sc_mtx);
-	usbd_xfer_set_stall(sc->sc_xfer[UMOSCOM_BULK_DT_WR]);
-	usbd_xfer_set_stall(sc->sc_xfer[UMOSCOM_BULK_DT_RD]);
+	usbd_xfer_set_zlp(sc->sc_xfer[UMOSCOM_BULK_DT_WR]);
 	mtx_unlock(&sc->sc_mtx);
 
 	error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc,
@@ -639,13 +638,16 @@ umoscom_write_callback(struct usb_xfer *xfer, usb_error_t error)
 tr_setup:
 		DPRINTF("\n");
 
+		if (usbd_xfer_get_and_clr_zlp(xfer))
+			break;
+
 		pc = usbd_xfer_get_frame(xfer, 0);
 		if (ucom_get_data(&sc->sc_ucom, pc, 0,
 		    UMOSCOM_BUFSIZE, &actlen)) {
 			usbd_xfer_set_frame_len(xfer, 0, actlen);
 			usbd_transfer_submit(xfer);
 		}
-		return;
+		break;
 
 	default:			/* Error */
 		if (error != USB_ERR_CANCELLED) {
@@ -654,7 +656,7 @@ tr_setup:
 			usbd_xfer_set_stall(xfer);
 			goto tr_setup;
 		}
-		return;
+		break;
 	}
 }
 
diff --git a/sys/dev/usb/serial/uplcom.c b/sys/dev/usb/serial/uplcom.c
index 9efac9ff6b7c..6808ddd64a4b 100644
--- a/sys/dev/usb/serial/uplcom.c
+++ b/sys/dev/usb/serial/uplcom.c
@@ -507,14 +507,12 @@ uplcom_attach(device_t dev)
 		goto detach;
 	}
 
-	if (sc->sc_chiptype == TYPE_PL2303) {
-		/* HX variants seem to lock up after a clear stall request. */
-		mtx_lock(&sc->sc_mtx);
-		usbd_xfer_set_stall(sc->sc_xfer[UPLCOM_BULK_DT_WR]);
-		usbd_xfer_set_stall(sc->sc_xfer[UPLCOM_BULK_DT_RD]);
-		mtx_unlock(&sc->sc_mtx);
-	} else if (sc->sc_chiptype == TYPE_PL2303HX ||
-		   sc->sc_chiptype == TYPE_PL2303HXD) {
+	mtx_lock(&sc->sc_mtx);
+	usbd_xfer_set_zlp(sc->sc_xfer[UPLCOM_BULK_DT_WR]);
+	mtx_unlock(&sc->sc_mtx);
+
+	if (sc->sc_chiptype == TYPE_PL2303HX ||
+	    sc->sc_chiptype == TYPE_PL2303HXD) {
 		/* reset upstream data pipes */
 		if (uplcom_pl2303_do(sc->sc_udev, UT_WRITE_VENDOR_DEVICE,
 		    UPLCOM_SET_REQUEST, 8, 0, 0) ||
@@ -1094,6 +1092,9 @@ uplcom_write_callback(struct usb_xfer *xfer, usb_error_t error)
 	case USB_ST_SETUP:
 	case USB_ST_TRANSFERRED:
 tr_setup:
+		if (usbd_xfer_get_and_clr_zlp(xfer))
+			break;
+
 		pc = usbd_xfer_get_frame(xfer, 0);
 		if (ucom_get_data(&sc->sc_ucom, pc, 0,
 		    UPLCOM_BULK_BUF_SIZE, &actlen)) {
@@ -1102,7 +1103,7 @@ tr_setup:
 			usbd_xfer_set_frame_len(xfer, 0, actlen);
 			usbd_transfer_submit(xfer);
 		}
-		return;
+		break;
 
 	default:			/* Error */
 		if (error != USB_ERR_CANCELLED) {
@@ -1110,7 +1111,7 @@ tr_setup:
 			usbd_xfer_set_stall(xfer);
 			goto tr_setup;
 		}
-		return;
+		break;
 	}
 }
 
diff --git a/sys/dev/usb/serial/uslcom.c b/sys/dev/usb/serial/uslcom.c
index fec18d2a5631..8c40a2c2fc3a 100644
--- a/sys/dev/usb/serial/uslcom.c
+++ b/sys/dev/usb/serial/uslcom.c
@@ -443,8 +443,7 @@ uslcom_attach(device_t dev)
 	}
 	/* clear stall at first run */
 	mtx_lock(&sc->sc_mtx);
-	usbd_xfer_set_stall(sc->sc_xfer[USLCOM_BULK_DT_WR]);
-	usbd_xfer_set_stall(sc->sc_xfer[USLCOM_BULK_DT_RD]);
+	usbd_xfer_set_zlp(sc->sc_xfer[USLCOM_BULK_DT_WR]);
 	mtx_unlock(&sc->sc_mtx);
 
 	sc->sc_partnum = uslcom_get_partnum(sc);
@@ -819,6 +818,9 @@ uslcom_write_callback(struct usb_xfer *xfer, usb_error_t error)
 	case USB_ST_SETUP:
 	case USB_ST_TRANSFERRED:
 tr_setup:
+		if (usbd_xfer_get_and_clr_zlp(xfer))
+			break;
+
 		pc = usbd_xfer_get_frame(xfer, 0);
 		if (ucom_get_data(&sc->sc_ucom, pc, 0,
 		    USLCOM_BULK_BUF_SIZE, &actlen)) {
@@ -827,7 +829,7 @@ tr_setup:
 			usbd_xfer_set_frame_len(xfer, 0, actlen);
 			usbd_transfer_submit(xfer);
 		}
-		return;
+		break;
 
 	default:			/* Error */
 		if (error != USB_ERR_CANCELLED) {
@@ -835,7 +837,7 @@ tr_setup:
 			usbd_xfer_set_stall(xfer);
 			goto tr_setup;
 		}
-		return;
+		break;
 	}
 }
 
diff --git a/sys/dev/usb/serial/uvscom.c b/sys/dev/usb/serial/uvscom.c
index 8bb69e4402cf..81fd5334a243 100644
--- a/sys/dev/usb/serial/uvscom.c
+++ b/sys/dev/usb/serial/uvscom.c
@@ -318,8 +318,7 @@ uvscom_attach(device_t dev)
 
 	/* clear stall at first run */
 	mtx_lock(&sc->sc_mtx);
-	usbd_xfer_set_stall(sc->sc_xfer[UVSCOM_BULK_DT_WR]);
-	usbd_xfer_set_stall(sc->sc_xfer[UVSCOM_BULK_DT_RD]);
+	usbd_xfer_set_zlp(sc->sc_xfer[UVSCOM_BULK_DT_WR]);
 	mtx_unlock(&sc->sc_mtx);
 
 	error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc,
@@ -391,13 +390,16 @@ uvscom_write_callback(struct usb_xfer *xfer, usb_error_t error)
 	case USB_ST_SETUP:
 	case USB_ST_TRANSFERRED:
 tr_setup:
+		if (usbd_xfer_get_and_clr_zlp(xfer))
+			break;
+
 		pc = usbd_xfer_get_frame(xfer, 0);
 		if (ucom_get_data(&sc->sc_ucom, pc, 0,
 		    UVSCOM_BULK_BUF_SIZE, &actlen)) {
 			usbd_xfer_set_frame_len(xfer, 0, actlen);
 			usbd_transfer_submit(xfer);
 		}
-		return;
+		break;
 
 	default:			/* Error */
 		if (error != USB_ERR_CANCELLED) {
@@ -405,7 +407,7 @@ tr_setup:
 			usbd_xfer_set_stall(xfer);
 			goto tr_setup;
 		}
-		return;
+		break;
 	}
 }
 
diff --git a/sys/dev/usb/usb_transfer.c b/sys/dev/usb/usb_transfer.c
index 436e08db14a1..e1582926b08d 100644
--- a/sys/dev/usb/usb_transfer.c
+++ b/sys/dev/usb/usb_transfer.c
@@ -2,7 +2,7 @@
 /*-
  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
  *
- * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
+ * Copyright (c) 2008-2021 Hans Petter Selasky. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -2689,6 +2689,61 @@ usbd_transfer_start_cb(void *arg)
 	}
 }
 
+/*------------------------------------------------------------------------*
+ *	usbd_xfer_set_zlp
+ *
+ * This function sets the USB transfers ZLP flag.
+ *------------------------------------------------------------------------*/
+void
+usbd_xfer_set_zlp(struct usb_xfer *xfer)
+{
+	if (xfer == NULL) {
+		/* tearing down */
+		return;
+	}
+	USB_XFER_LOCK_ASSERT(xfer, MA_OWNED);
+
+	/* avoid any races by locking the USB mutex */
+	USB_BUS_LOCK(xfer->xroot->bus);
+	xfer->flags.send_zlp = 1;
+	USB_BUS_UNLOCK(xfer->xroot->bus);
+}
+
+/*------------------------------------------------------------------------*
+ *	usbd_xfer_get_and_clr_zlp
+ *
+ * This function gets and clears the USB transfers ZLP flag and
+ * queues a zero-length USB transfer if the flag was set.
+ *------------------------------------------------------------------------*/
+uint8_t
+usbd_xfer_get_and_clr_zlp(struct usb_xfer *xfer)
+{
+	uint8_t retval;
+
+	if (xfer == NULL) {
+		/* tearing down */
+		return (0);
+	}
+	USB_XFER_LOCK_ASSERT(xfer, MA_OWNED);
+
+	retval = xfer->flags.send_zlp;
+
+	if (retval != 0) {
+		DPRINTFN(1, "Sending zero-length packet.\n");
+
+		/* avoid any races by locking the USB mutex */
+		USB_BUS_LOCK(xfer->xroot->bus);
+		xfer->flags.send_zlp = 0;
+		USB_BUS_UNLOCK(xfer->xroot->bus);
+
+		/* queue up a zero-length packet */
+		usbd_xfer_set_frame_len(xfer, 0, 0);
+		usbd_xfer_set_frames(xfer, 1);
+		usbd_transfer_submit(xfer);
+	}
+	return (retval);
+}
+
 /*------------------------------------------------------------------------*
  *	usbd_xfer_set_stall
  *
@@ -2733,9 +2788,7 @@ usbd_transfer_clear_stall(struct usb_xfer *xfer)
 
 	/* avoid any races by locking the USB mutex */
 	USB_BUS_LOCK(xfer->xroot->bus);
-
 	xfer->flags.stall_pipe = 0;
-
 	USB_BUS_UNLOCK(xfer->xroot->bus);
 }
 
diff --git a/sys/dev/usb/usbdi.h b/sys/dev/usb/usbdi.h
index 1b3b4af5f717..287e40d59364 100644
--- a/sys/dev/usb/usbdi.h
+++ b/sys/dev/usb/usbdi.h
@@ -218,6 +218,7 @@ struct usb_xfer_flags {
 					 * option only has effect for
 					 * ISOCHRONOUS transfers.
 					 */
+	uint8_t send_zlp:1;		/* send a zero length packet first */
 };
 
 /*
@@ -655,6 +656,8 @@ void	usbd_xfer_set_frame_len(struct usb_xfer *xfer, usb_frcount_t frindex,
 	    usb_frlength_t len);
 void	usbd_xfer_set_timeout(struct usb_xfer *xfer, int timeout);
 void	usbd_xfer_set_frames(struct usb_xfer *xfer, usb_frcount_t n);
+void	usbd_xfer_set_zlp(struct usb_xfer *xfer);
+uint8_t	usbd_xfer_get_and_clr_zlp(struct usb_xfer *xfer);
 void	usbd_xfer_set_stall(struct usb_xfer *xfer);
 int	usbd_xfer_is_stalled(struct usb_xfer *xfer);
 void	usbd_xfer_set_flag(struct usb_xfer *xfer, int flag);


More information about the dev-commits-src-main mailing list