⬢ DragonFlyBSD Kernel Audit
DF-5382 / fix.diff
← back to finding ↓ download raw
--- a/sys/bus/u4b/serial/umcs.c
+++ b/sys/bus/u4b/serial/umcs.c
@@ -138,6 +138,21 @@
 	uint8_t	sc_numports;		/* Number of ports (subunits) */
 	struct umcs7840_softc_oneport sc_ports[UMCS7840_MAX_PORTS];	/* Indexed by PHYSICAL
 									 * port number. */
+
+	/*
+	 * DF-5382: LSR/MSR refresh triggered by interrupt data must not
+	 * run on the shared per-bus USB callback thread.  The interrupt
+	 * callback only records the pending ports and defers the
+	 * synchronous control requests to the ucom super taskqueue, where
+	 * synchronous requests are the sanctioned pattern
+	 * (usbd_do_request_proc contract).
+	 */
+	uint8_t	sc_status_pending;	/* mask of PHYSICAL port numbers */
+	uint8_t	sc_status_isrc[UMCS7840_MAX_PORTS];	/* captured ISR bytes */
+	struct umcs7840_status_msg {
+		struct usb_proc_msg hdr;
+		struct umcs7840_softc *sc;
+	} sc_status_msg[2];
 };
 
 /* prototypes */
@@ -184,6 +199,9 @@
 static void umcs7840_read_callbackN(struct usb_xfer *, usb_error_t, uint8_t);
 static void umcs7840_write_callbackN(struct usb_xfer *, usb_error_t, uint8_t);
 
+	static void umcs7840_cfg_status_refresh(struct usb_proc_msg *);
+	static void umcs7840_queue_status_refresh(struct umcs7840_softc *);
+
 /* Indexed by LOGICAL port number (subunit), so two-port device uses 0 & 1 */
 static usb_callback_t *umcs7840_rw_callbacks[UMCS7840_MAX_PORTS][UMCS7840_N_TRANSFERS] = {
 	{&umcs7840_read_callback1, &umcs7840_write_callback1},
@@ -748,28 +766,14 @@
 				if (buf[pn] & MCS7840_UART_ISR_NOPENDING)
 					continue;
 				DPRINTF("Port %d has pending interrupt: %02x (FIFO: %02x)\n", pn, buf[pn] & MCS7840_UART_ISR_INTMASK, buf[pn] & (~MCS7840_UART_ISR_INTMASK));
-				switch (buf[pn] & MCS7840_UART_ISR_INTMASK) {
-				case MCS7840_UART_ISR_RXERR:
-				case MCS7840_UART_ISR_RXHASDATA:
-				case MCS7840_UART_ISR_RXTIMEOUT:
-					/* Read new LSR */
-					if (umcs7840_get_UART_reg_sync(sc, pn, MCS7840_UART_REG_LSR, &sc->sc_ports[pn].sc_lsr))
-						break;	/* Inner switch */
-					ucom_status_change(&sc->sc_ucom[subunit]);
-					/* Inner switch */
-					break;
-				case MCS7840_UART_ISR_TXEMPTY:
-					/* Do nothing */
-					break;	/* Inner switch */
-				case MCS7840_UART_ISR_MSCHANGE:
-					/* Read new MSR */
-					if (umcs7840_get_UART_reg_sync(sc, pn, MCS7840_UART_REG_MSR, &sc->sc_ports[pn].sc_msr))
-						break;	/* Inner switch */
-					DPRINTF("Port %d: new MSR %02x\n", pn, sc->sc_ports[pn].sc_msr);
-					ucom_status_change(&sc->sc_ucom[subunit]);
-					break;
-				}
+				/* DF-5382: defer, do not issue synchronous
+				 * control requests from the per-bus USB callback
+				 * thread */
+				sc->sc_status_isrc[pn] = buf[pn];
+				sc->sc_status_pending |= (1U << pn);
 			}
+			if (sc->sc_status_pending)
+				umcs7840_queue_status_refresh(sc);
 		} else
 			device_printf(sc->sc_dev, "Invalid interrupt data length %d", actlen);
 		/* FALLTHROUGH */
@@ -789,6 +793,60 @@
 	}
 }
 
+/*
+ * Runs on sc_super_ucom.sc_tq with sc->sc_lock held -- the same context
+ * and contract as the other ucom cfg tasks: synchronous control requests
+ * are allowed here, unlike in the xfer callback on the per-bus thread.
+ */
+static void
+umcs7840_cfg_status_refresh(struct usb_proc_msg *_msg)
+{
+	struct umcs7840_status_msg *msg = (struct umcs7840_status_msg *)_msg;
+	struct umcs7840_softc *sc = msg->sc;
+	int subunit;
+
+	for (subunit = 0; subunit < sc->sc_numports; ++subunit) {
+		uint8_t pn = sc->sc_ucom[subunit].sc_portno;
+
+		if (!(sc->sc_status_pending & (1U << pn)))
+			continue;
+		sc->sc_status_pending &= ~(1U << pn);
+		switch (sc->sc_status_isrc[pn] & MCS7840_UART_ISR_INTMASK) {
+		case MCS7840_UART_ISR_RXERR:
+		case MCS7840_UART_ISR_RXHASDATA:
+		case MCS7840_UART_ISR_RXTIMEOUT:
+			/* Read new LSR */
+			if (umcs7840_get_UART_reg_sync(sc, pn,
+			    MCS7840_UART_REG_LSR, &sc->sc_ports[pn].sc_lsr) == 0)
+				ucom_status_change(&sc->sc_ucom[subunit]);
+			break;
+		case MCS7840_UART_ISR_TXEMPTY:
+			/* Do nothing */
+			break;
+		case MCS7840_UART_ISR_MSCHANGE:
+			/* Read new MSR */
+			if (umcs7840_get_UART_reg_sync(sc, pn,
+			    MCS7840_UART_REG_MSR, &sc->sc_ports[pn].sc_msr) == 0)
+				ucom_status_change(&sc->sc_ucom[subunit]);
+			break;
+		}
+	}
+}
+
+/* called from the interrupt xfer callback with sc->sc_lock held */
+static void
+umcs7840_queue_status_refresh(struct umcs7840_softc *sc)
+{
+	struct umcs7840_status_msg *msg;
+
+	if (usb_proc_is_gone(&sc->sc_super_ucom.sc_tq))
+		return;			/* detaching */
+	msg = usb_proc_msignal(&sc->sc_super_ucom.sc_tq,
+	    &sc->sc_status_msg[0].hdr, &sc->sc_status_msg[1].hdr);
+	msg->hdr.pm_callback = &umcs7840_cfg_status_refresh;
+	msg->sc = sc;
+}
+
 static void
 umcs7840_read_callback1(struct usb_xfer *xfer, usb_error_t error)
 {