[PATCH net-next v4 1/3] net: wwan: core: propagate modem control signals to port drivers
Peter Hunt <[email protected]>
| Newsgroups | org.kernel.vger.linux-arm-msm,dev.linux.lists.mhi,org.kernel.vger.linux-kernel,org.kernel.vger.netdev |
|---|---|
| Message-ID | <[email protected]> |
The WWAN character device emulates the TTY modem-control ioctls (TIOCMGET/TIOCMSET/TIOCMBIC/TIOCMBIS) for AT and QCDM ports, but the result is only stored in port->at_data.mdmbits and never reaches the port driver. A driver therefore cannot act on the host raising or dropping DTR/RTS, even though some modems depend on it (e.g. they withhold unsolicited AT result codes until the host asserts DTR). Add an optional ->dtr_rts(port, on) operation to struct wwan_port_ops, mirroring tty_port_operations.dtr_rts. Drivers that implement it receive a simple assert/de-assert signal while the TIOCM bitmask state is tracked by the wwan core. TIOCMSET/TIOCMBIC/TIOCMBIS resolve the new bitmask and call ->dtr_rts when the driver implements it, gated on WWAN_PORT_AT to match the open/close raise/drop behaviour below. The boolean passed reflects DTR only, since DTR is the line that governs URC gating on modems supporting this signalling; RTS is tracked in mdmbits but driven together with DTR on open/close. Also raise DTR/RTS in wwan_port_op_start on first open of an AT port when the driver implements ->dtr_rts, and drop them in wwan_port_op_stop on last close. This mirrors TTY semantics (DTR is asserted on open) and means individual drivers do not need to implement this themselves. at_data.mdmbits is protected by data_lock; the ->dtr_rts call is made after releasing data_lock and re-acquiring ops_lock so that it is serialised against port removal (which nulls port->ops under ops_lock). Signed-off-by: Peter Hunt <[email protected]> --- v4: Protect at_data.mdmbits in wwan_port_op_start/stop under data_lock; release data_lock and acquire ops_lock with a NULL check before calling ->dtr_rts from the ioctl path; gate ioctl ->dtr_rts on WWAN_PORT_AT to match open/close behaviour; reduce boolean to TIOCM_DTR only v3: Replace ->tiocmget/->tiocmset with ->dtr_rts(port, bool on) modelled on tty_port_operations.dtr_rts; raise/drop DTR/RTS in wwan_port_op_start/stop rather than in the driver (Loic Poulain) --- drivers/net/wwan/wwan_core.c | 31 ++++++++++++++++++++++++++++++- include/linux/wwan.h | 3 +++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/drivers/net/wwan/wwan_core.c b/drivers/net/wwan/wwan_core.c index ffbcf11e4e68..90ca85a63b5a 100644 --- a/drivers/net/wwan/wwan_core.c +++ b/drivers/net/wwan/wwan_core.c @@ -759,8 +759,17 @@ static int wwan_port_op_start(struct wwan_port *port) if (!port->start_count) ret = port->ops->start(port); - if (!ret) + if (!ret) { port->start_count++; + /* Mirror TTY semantics: raise DTR/RTS on first open of an AT port */ + if (port->start_count == 1 && port->type == WWAN_PORT_AT && + port->ops->dtr_rts) { + mutex_lock(&port->data_lock); + port->at_data.mdmbits |= TIOCM_DTR | TIOCM_RTS; + mutex_unlock(&port->data_lock); + port->ops->dtr_rts(port, true); + } + } out_unlock: mutex_unlock(&port->ops_lock); @@ -773,6 +782,13 @@ static void wwan_port_op_stop(struct wwan_port *port) mutex_lock(&port->ops_lock); port->start_count--; if (!port->start_count) { + /* Mirror TTY semantics: drop DTR/RTS on last close of an AT port */ + if (port->ops && port->type == WWAN_PORT_AT && port->ops->dtr_rts) { + mutex_lock(&port->data_lock); + port->at_data.mdmbits &= ~(TIOCM_DTR | TIOCM_RTS); + mutex_unlock(&port->data_lock); + port->ops->dtr_rts(port, false); + } if (port->ops) port->ops->stop(port); skb_queue_purge(&port->rxq); @@ -980,6 +996,8 @@ static long wwan_port_fops_at_ioctl(struct wwan_port *port, unsigned int cmd, unsigned long arg) { int ret = 0; + bool call_dtr_rts = false; + bool dtr_on = false; mutex_lock(&port->data_lock); @@ -1036,6 +1054,10 @@ static long wwan_port_fops_at_ioctl(struct wwan_port *port, unsigned int cmd, port->at_data.mdmbits |= mdmbits; else port->at_data.mdmbits = mdmbits; + if (port->type == WWAN_PORT_AT) { + dtr_on = !!(port->at_data.mdmbits & TIOCM_DTR); + call_dtr_rts = true; + } break; } @@ -1061,6 +1083,13 @@ static long wwan_port_fops_at_ioctl(struct wwan_port *port, unsigned int cmd, mutex_unlock(&port->data_lock); + if (call_dtr_rts) { + mutex_lock(&port->ops_lock); + if (port->ops && port->ops->dtr_rts) + port->ops->dtr_rts(port, dtr_on); + mutex_unlock(&port->ops_lock); + } + return ret; } diff --git a/include/linux/wwan.h b/include/linux/wwan.h index 1e0e2cb53579..a7684950d73b 100644 --- a/include/linux/wwan.h +++ b/include/linux/wwan.h @@ -57,6 +57,8 @@ struct wwan_port; * @tx_blocking: Optional blocking routine that sends WWAN port protocol data * to the device. * @tx_poll: Optional routine that sets additional TX poll flags. + * @dtr_rts: Optional routine that asserts (on=true) or de-asserts (on=false) + * the DTR and RTS modem control lines. * * The wwan_port_ops structure contains a list of low-level operations * that control a WWAN port device. All functions are mandatory unless specified. @@ -70,6 +72,7 @@ struct wwan_port_ops { int (*tx_blocking)(struct wwan_port *port, struct sk_buff *skb); __poll_t (*tx_poll)(struct wwan_port *port, struct file *filp, poll_table *wait); + void (*dtr_rts)(struct wwan_port *port, bool on); }; /** struct wwan_port_caps - The WWAN port capbilities -- 2.43.0