git: 36a80f426435 - main - usb: serial: make more commands execute synchronously

From: Kyle Evans <kevans_at_FreeBSD.org>
Date: Wed, 11 Dec 2024 01:24:49 UTC
The branch main has been updated by kevans:

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

commit 36a80f4264350a2f4f0686eb91ae7f5943d40327
Author:     Kyle Evans <kevans@FreeBSD.org>
AuthorDate: 2024-12-11 01:23:10 +0000
Commit:     Kyle Evans <kevans@FreeBSD.org>
CommitDate: 2024-12-11 01:23:41 +0000

    usb: serial: make more commands execute synchronously
    
    The termios layer wants some level of guarantee that we've actually
    submitted param changes to the hardware when our functions return, so we
    need to do a little more waiting to avoid violating those guarantees.
    
    This is especially important as some hardware has some minimum timing
    specifications around this stuff, and without being less asynchronous
    the software dealing with these devices can't reasonably operate the
    hardware without more excessive delays than they should need.
    
    More specifically, we make sure that:
     - The command to start transfers is finished before we toggle DTR/RTS
     - The status_change command finishes before we return, which may change
        some fields in the softc that we need for a subsequent call into
        usb_serial
     - cfg_param finishes before we re-enable transfers, and we ensure that
        RTS is appropriately toggled before we return to userland
    
    This has been observed to fix some flakiness in connecting to some
    ESP32 devices.
    
    Tested by:      kenrap from Libera
    Reviewed by:    imp, kib
    Differential Revision:  https://reviews.freebsd.org/D47952
---
 sys/dev/usb/serial/usb_serial.c | 69 +++++++++++++++++++++++++++++++++--------
 1 file changed, 56 insertions(+), 13 deletions(-)

diff --git a/sys/dev/usb/serial/usb_serial.c b/sys/dev/usb/serial/usb_serial.c
index 30a0c6203086..dfae051ecfd6 100644
--- a/sys/dev/usb/serial/usb_serial.c
+++ b/sys/dev/usb/serial/usb_serial.c
@@ -153,7 +153,7 @@ static int	ucom_attach_tty(struct ucom_super_softc *, struct ucom_softc *);
 static void	ucom_detach_tty(struct ucom_super_softc *, struct ucom_softc *);
 static int	ucom_queue_command(struct ucom_softc *,
 		    usb_proc_callback_t *, struct termios *pt,
-		    struct usb_proc_msg *t0, struct usb_proc_msg *t1);
+		    struct usb_proc_msg *t0, struct usb_proc_msg *t1, bool wait);
 static void	ucom_shutdown(struct ucom_softc *);
 static void	ucom_ring(struct ucom_softc *, uint8_t);
 static void	ucom_break(struct ucom_softc *, uint8_t);
@@ -592,10 +592,43 @@ ucom_set_usb_mode(struct ucom_super_softc *ssc, enum usb_hc_mode usb_mode)
 	}
 }
 
+static void
+ucom_command_barrier_cb(struct usb_proc_msg *msg __unused)
+{
+	/* NOP */
+}
+
+/*
+ * ucom_command_barrier inserts a dummy task and waits for it so that we can be
+ * certain that previously enqueued tasks are finished before returning back to
+ * the tty layer.
+ */
+static int
+ucom_command_barrier(struct ucom_softc *sc)
+{
+	struct ucom_super_softc *ssc = sc->sc_super;
+	struct usb_proc_msg dummy = { .pm_callback = ucom_command_barrier_cb };
+	struct usb_proc_msg *task;
+	int error;
+
+	UCOM_MTX_ASSERT(sc, MA_OWNED);
+
+	if (usb_proc_is_gone(&ssc->sc_tq)) {
+		DPRINTF("proc is gone\n");
+		return (ENXIO);         /* nothing to do */
+	}
+
+	task = usb_proc_msignal(&ssc->sc_tq, &dummy, &dummy);
+	error = usb_proc_mwait_sig(&ssc->sc_tq, task, task);
+	if (error == 0 && sc->sc_tty != NULL && tty_gone(sc->sc_tty))
+		error = ENXIO;
+	return (error);
+}
+
 static int
 ucom_queue_command(struct ucom_softc *sc,
     usb_proc_callback_t *fn, struct termios *pt,
-    struct usb_proc_msg *t0, struct usb_proc_msg *t1)
+    struct usb_proc_msg *t0, struct usb_proc_msg *t1, bool wait)
 {
 	struct ucom_super_softc *ssc = sc->sc_super;
 	struct ucom_param_task *task;
@@ -629,7 +662,7 @@ ucom_queue_command(struct ucom_softc *sc,
 	/*
 	 * Closing or opening the device should be synchronous.
 	 */
-	if (fn == ucom_cfg_close || fn == ucom_cfg_open) {
+	if (wait) {
 		error = usb_proc_mwait_sig(&ssc->sc_tq, t0, t1);
 
 		/* usb_proc_mwait_sig may have dropped the tty lock. */
@@ -793,14 +826,17 @@ ucom_open(struct tty *tp)
 
 	error = ucom_queue_command(sc, ucom_cfg_open, NULL,
 	    &sc->sc_open_task[0].hdr,
-	    &sc->sc_open_task[1].hdr);
+	    &sc->sc_open_task[1].hdr, true);
 	if (error != 0)
 		goto out;
 
-	/* Queue transfer enable command last */
+	/*
+	 * Queue transfer enable command last, we'll have a barrier later so we
+	 * don't need to wait on this to complete specifically.
+	 */
 	error = ucom_queue_command(sc, ucom_cfg_start_transfers, NULL,
 	    &sc->sc_start_task[0].hdr,
-	    &sc->sc_start_task[1].hdr);
+	    &sc->sc_start_task[1].hdr, true);
 	if (error != 0)
 		goto out;
 
@@ -813,6 +849,7 @@ ucom_open(struct tty *tp)
 
 	ucom_status_change(sc);
 
+	error = ucom_command_barrier(sc);
 out:
 	return (error);
 }
@@ -852,7 +889,7 @@ ucom_close(struct tty *tp)
 
 	(void)ucom_queue_command(sc, ucom_cfg_close, NULL,
 	    &sc->sc_close_task[0].hdr,
-	    &sc->sc_close_task[1].hdr);
+	    &sc->sc_close_task[1].hdr, true);
 
 	sc->sc_flag &= ~(UCOM_FLAG_HL_READY | UCOM_FLAG_RTS_IFLOW);
 
@@ -933,11 +970,15 @@ ucom_ioctl(struct tty *tp, u_long cmd, caddr_t data, struct thread *td)
 #endif
 	case TIOCSBRK:
 		ucom_break(sc, 1);
-		error = 0;
+		error = ucom_command_barrier(sc);
+		if (error == ENXIO)
+			error = ENODEV;
 		break;
 	case TIOCCBRK:
 		ucom_break(sc, 0);
-		error = 0;
+		error = ucom_command_barrier(sc);
+		if (error == ENXIO)
+			error = ENODEV;
 		break;
 	default:
 		if (sc->sc_callback->ucom_ioctl) {
@@ -1097,7 +1138,7 @@ ucom_line_state(struct ucom_softc *sc,
 	 */
 	(void)ucom_queue_command(sc, ucom_cfg_line_state, NULL,
 	    &sc->sc_line_state_task[0].hdr,
-	    &sc->sc_line_state_task[1].hdr);
+	    &sc->sc_line_state_task[1].hdr, false);
 }
 
 static void
@@ -1255,7 +1296,7 @@ ucom_status_change(struct ucom_softc *sc)
 
 	(void)ucom_queue_command(sc, ucom_cfg_status_change, NULL,
 	    &sc->sc_status_task[0].hdr,
-	    &sc->sc_status_task[1].hdr);
+	    &sc->sc_status_task[1].hdr, true);
 }
 
 static void
@@ -1329,14 +1370,14 @@ ucom_param(struct tty *tp, struct termios *t)
 	/* Queue baud rate programming command first */
 	error = ucom_queue_command(sc, ucom_cfg_param, t,
 	    &sc->sc_param_task[0].hdr,
-	    &sc->sc_param_task[1].hdr);
+	    &sc->sc_param_task[1].hdr, true);
 	if (error != 0)
 		goto done;
 
 	/* Queue transfer enable command last */
 	error = ucom_queue_command(sc, ucom_cfg_start_transfers, NULL,
 	    &sc->sc_start_task[0].hdr,
-	    &sc->sc_start_task[1].hdr);
+	    &sc->sc_start_task[1].hdr, true);
 	if (error != 0)
 		goto done;
 
@@ -1346,6 +1387,8 @@ ucom_param(struct tty *tp, struct termios *t)
 		sc->sc_flag &= ~UCOM_FLAG_RTS_IFLOW;
 		ucom_modem(tp, SER_RTS, 0);
 	}
+
+	error = ucom_command_barrier(sc);
 done:
 	if (error) {
 		if (opened) {