From: Peter Hunt <peter.hunt@opengear.com>
To: loic.poulain@oss.qualcomm.com, mani@kernel.org, ryazanov.s.a@gmail.com
Cc: johannes@sipsolutions.net, netdev@vger.kernel.org,
mhi@lists.linux.dev, linux-arm-msm@vger.kernel.org,
linux-kernel@vger.kernel.org,
Peter Hunt <peter.hunt@opengear.com>
Subject: [PATCH net-next v5 1/3] net: wwan: core: propagate modem control signals to port drivers
Date: Thu, 20 Aug 2026 08:49:25 +1000 [thread overview]
Message-ID: <20260819224927.2274790-2-peter.hunt@opengear.com> (raw)
In-Reply-To: <20260819224927.2274790-1-peter.hunt@opengear.com>
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, mdmbits) operation to struct wwan_port_ops.
Drivers that implement it receive the full TIOCM bitmask so they can assert
or de-assert DTR and RTS independently. The wwan core tracks the full TIOCM
bitmask in port->at_data.mdmbits and calls ->dtr_rts when it changes, gated
on WWAN_PORT_AT to match the open/close raise/drop behaviour.
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. In the ioctl path the ->dtr_rts
call is deferred until ops_lock is held, where mdmbits is re-read under
data_lock, so the value passed to the driver always reflects the committed
bitmask under ops_lock and is serialised against concurrent ioctls and
against port removal (which nulls port->ops under ops_lock).
wwan_remove_port() is also updated to call ->dtr_rts(port, 0) before
->stop() when a port is removed while still open, mirroring the last-close
de-assert path.
Signed-off-by: Peter Hunt <peter.hunt@opengear.com>
---
v5: Change ->dtr_rts signature from bool to unsigned int mdmbits so DTR
and RTS can be driven independently; re-read mdmbits inside ops_lock
in the ioctl path to close a concurrent-ioctl ordering race; add
de-assert call to wwan_remove_port() for the hot-unplug case; update
kernel-doc to note the op is AT-only and describe the mdmbits argument
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 | 40 +++++++++++++++++++++++++++++++++++-
include/linux/wwan.h | 3 +++
2 files changed, 42 insertions(+), 1 deletion(-)
diff --git a/drivers/net/wwan/wwan_core.c b/drivers/net/wwan/wwan_core.c
index ffbcf11e4e68..3a6a0c5a3acf 100644
--- a/drivers/net/wwan/wwan_core.c
+++ b/drivers/net/wwan/wwan_core.c
@@ -685,6 +685,12 @@ void wwan_remove_port(struct wwan_port *port)
mutex_lock(&port->ops_lock);
if (port->start_count) {
+ if (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, 0);
+ }
port->ops->stop(port);
port->start_count = 0;
}
@@ -759,8 +765,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, port->at_data.mdmbits);
+ }
+ }
out_unlock:
mutex_unlock(&port->ops_lock);
@@ -773,6 +788,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, port->at_data.mdmbits);
+ }
if (port->ops)
port->ops->stop(port);
skb_queue_purge(&port->rxq);
@@ -980,6 +1002,7 @@ 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;
mutex_lock(&port->data_lock);
@@ -1036,6 +1059,8 @@ 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)
+ call_dtr_rts = true;
break;
}
@@ -1061,6 +1086,19 @@ static long wwan_port_fops_at_ioctl(struct wwan_port *port, unsigned int cmd,
mutex_unlock(&port->data_lock);
+ if (call_dtr_rts) {
+ unsigned int bits;
+
+ mutex_lock(&port->ops_lock);
+ if (port->ops && port->ops->dtr_rts) {
+ mutex_lock(&port->data_lock);
+ bits = port->at_data.mdmbits;
+ mutex_unlock(&port->data_lock);
+ port->ops->dtr_rts(port, bits);
+ }
+ mutex_unlock(&port->ops_lock);
+ }
+
return ret;
}
diff --git a/include/linux/wwan.h b/include/linux/wwan.h
index 1e0e2cb53579..57406139304e 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 updates the modem control lines to match
+ * @mdmbits (a TIOCM_* bitmask). Only called for WWAN_PORT_AT ports.
*
* 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, unsigned int mdmbits);
};
/** struct wwan_port_caps - The WWAN port capbilities
--
2.43.0
next prev parent reply other threads:[~2026-08-19 22:50 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-19 22:49 [PATCH net-next v5 0/3] net: wwan: support DTR/RTS on AT ports via MHI IP_CTRL Peter Hunt
2026-08-19 22:49 ` Peter Hunt [this message]
2026-08-19 22:49 ` [PATCH net-next v5 2/3] bus: mhi: host: pci_generic: enumerate IP_CTRL channel for Sierra EM919x/EM929x Peter Hunt
2026-08-19 22:49 ` [PATCH net-next v5 3/3] net: wwan: mhi_wwan_ctrl: drive DTR/RTS via the IP_CTRL channel Peter Hunt
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260819224927.2274790-2-peter.hunt@opengear.com \
--to=peter.hunt@opengear.com \
--cc=johannes@sipsolutions.net \
--cc=linux-arm-msm@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=loic.poulain@oss.qualcomm.com \
--cc=mani@kernel.org \
--cc=mhi@lists.linux.dev \
--cc=netdev@vger.kernel.org \
--cc=ryazanov.s.a@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox