--- 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) {