From: Jakub Kicinski <kuba@kernel.org>
To: peter.hunt@opengear.com
Cc: Jakub Kicinski <kuba@kernel.org>,
loic.poulain@oss.qualcomm.com, ryazanov.s.a@gmail.com,
mani@kernel.org, johannes@sipsolutions.net,
netdev@vger.kernel.org, mhi@lists.linux.dev,
linux-arm-msm@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: Re: [PATCH net-next v4 1/3] net: wwan: core: propagate modem control signals to port drivers
Date: Tue, 18 Aug 2026 10:09:26 -0700 [thread overview]
Message-ID: <20260818170926.4019431-1-kuba@kernel.org> (raw)
In-Reply-To: <20260816121705.858013-2-peter.hunt@opengear.com>
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
net: wwan: core: propagate modem control signals to port drivers
Adds an optional ->dtr_rts(port, on) callback to struct wwan_port_ops so
port drivers learn when the host asserts or drops DTR/RTS. The wwan core
keeps tracking the TIOCM bitmask in port->at_data.mdmbits and now calls the
new op from the TIOCMSET/TIOCMBIC/TIOCMBIS ioctls as well as on first open
and last close of an AT port.
A few questions below.
> diff --git a/drivers/net/wwan/wwan_core.c b/drivers/net/wwan/wwan_core.c
> index ffbcf11e4e681..90ca85a63b5a3 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);
> + }
[Severity: Low]
Can the dtr_rts(true) issued by wwan_port_op_start() go unmatched when the
port is removed while an fd is still open? wwan_remove_port() does not go
through wwan_port_op_stop():
drivers/net/wwan/wwan_core.c:wwan_remove_port() {
mutex_lock(&port->ops_lock);
if (port->start_count) {
port->ops->stop(port);
port->start_count = 0;
}
port->ops = NULL; /* Prevent any new port operations (e.g. from fops) */
mutex_unlock(&port->ops_lock);
...
}
So ->dtr_rts(port, false) is never called and at_data.mdmbits keeps
TIOCM_DTR|TIOCM_RTS visible to TIOCMGET on the still-open fd.
The later close() cannot repair it either, since the unconditional
port->start_count-- in wwan_port_op_stop() underflows the already-zeroed
unsigned counter to UINT_MAX and the new de-assert block sits inside
if (!port->start_count). The underflow itself isn't introduced by this
patch, but the new assert/de-assert pair inherits it.
Would it make sense to call ->dtr_rts(port, false) (and clear mdmbits) in
the same branch of wwan_remove_port() that calls ->stop(), or to document
that ->stop() implies de-assert?
> 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;
> + }
[Severity: Medium]
Does this drive RTS in the opposite direction of what userspace asked for?
The kernel-doc added to struct wwan_port_ops says the boolean asserts or
de-asserts "the DTR and RTS modem control lines", and the two call sites in
wwan_port_op_start()/wwan_port_op_stop() do treat the pair together
(TIOCM_DTR | TIOCM_RTS). Here the boolean is derived from the DTR bit
alone, and the TIOCM_RTS bit the caller just changed is dropped.
The first in-tree implementer added later in this series expands the boolean
back to both lines:
drivers/net/wwan/mhi_wwan_ctrl.c:mhi_wwan_ctrl_dtr_rts() {
mhi_wwan_ctrl_send_dtr(wwan_port_get_drvdata(port),
on ? TIOCM_DTR | TIOCM_RTS : 0);
}
so on an AT port:
TIOCMBIS(TIOCM_RTS) with DTR clear -> mdmbits shows RTS set,
dtr_rts(false) clears RTS on the wire
TIOCMBIC(TIOCM_RTS) with DTR set -> mdmbits clears RTS,
dtr_rts(true) re-asserts RTS
TIOCMBIC(TIOCM_DTR) with RTS set -> both lines dropped while TIOCMGET
still reports TIOCM_RTS
TIOCMGET then reports a state that contradicts what the driver was last told
to drive, and nothing re-synchronises the two until some later ioctl happens
to change DTR. The commit message does say "The boolean passed reflects DTR
only", but should the kernel-doc say the same thing, or should the op carry
the bitmask so RTS can be driven independently?
> 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);
> + }
> +
[Severity: Medium]
Can two concurrent TIOCM ioctls leave mdmbits and the line state
permanently inconsistent here?
port->at_data.mdmbits is updated and dtr_on is sampled under data_lock, but
the callback is issued after data_lock is dropped and ops_lock is taken, so
the two updates happen in disjoint critical sections under different
mutexes. The handler is registered as .unlocked_ioctl in wwan_port_fops, so
two threads (or two fds) can be inside wwan_port_fops_at_ioctl() at once:
CPU0: TIOCMBIC(TIOCM_DTR)
port->at_data.mdmbits &= ~TIOCM_DTR;
dtr_on = false;
mutex_unlock(&port->data_lock);
/* preempted before taking ops_lock */
CPU1: TIOCMBIS(TIOCM_DTR)
port->at_data.mdmbits |= TIOCM_DTR;
dtr_on = true;
mutex_unlock(&port->data_lock);
port->ops->dtr_rts(port, true);
CPU0 resumes:
port->ops->dtr_rts(port, false);
Final state: TIOCMGET reports TIOCM_DTR asserted while the driver was last
told to de-assert DTR, which is the exact condition the patch is meant to
fix (modem withholds URCs while userspace believes DTR is raised).
The commit message's justification, "the ->dtr_rts call is made after
releasing data_lock and re-acquiring ops_lock so that it is serialised
against port removal", covers port->ops becoming NULL but not the ordering
of the bitmask and the line. Would re-reading mdmbits under data_lock while
holding ops_lock work, i.e. nesting data_lock inside ops_lock as
wwan_port_op_start()/wwan_port_op_stop() already do in this same patch?
> return ret;
> }
>
> diff --git a/include/linux/wwan.h b/include/linux/wwan.h
> index 1e0e2cb535799..a7684950d73b0 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.
[Severity: Low]
Should this note that the op is only ever invoked for WWAN_PORT_AT?
All three new call sites in wwan_core.c are gated on
port->type == WWAN_PORT_AT, but the TIOCM emulation they hook into is shared
with QCDM ports:
drivers/net/wwan/wwan_core.c:wwan_port_fops_ioctl() {
if (port->type == WWAN_PORT_AT || port->type == WWAN_PORT_QCDM) {
/* AT and QCDM port specific IOCTLs */
res = wwan_port_fops_at_ioctl(port, cmd, arg);
...
}
For a QCDM port the core still mutates port->at_data.mdmbits on
TIOCMSET/TIOCMBIC/TIOCMBIS but never calls ->dtr_rts, so a QCDM driver that
implements the documented callback gets a hook that never fires while the
TIOCMGET-visible state drifts from the hardware.
Cross-instance finding from sashiko-gemini (1f1883cdaae2b1f372b000af391d04e91947cf2c647012be2e4aa3916c3a8822):
[Severity: Medium]
TTY emulation behavior for `TIOCMGET` incorrectly depends on the hardware driver's implementation of `dtr_rts`
--
pw-bot: cr
next prev parent reply other threads:[~2026-08-18 17:09 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-16 12:17 [PATCH net-next v4 0/3] net: wwan: support DTR/RTS on AT ports via MHI IP_CTRL Peter Hunt
2026-08-16 12:17 ` [PATCH net-next v4 1/3] net: wwan: core: propagate modem control signals to port drivers Peter Hunt
2026-08-18 17:09 ` Jakub Kicinski [this message]
2026-08-16 12:17 ` [PATCH net-next v4 2/3] bus: mhi: host: pci_generic: enumerate IP_CTRL channel for Sierra EM919x/EM929x Peter Hunt
2026-08-16 12:17 ` [PATCH net-next v4 3/3] net: wwan: mhi_wwan_ctrl: drive DTR/RTS via the IP_CTRL channel Peter Hunt
2026-08-18 17:09 ` Jakub Kicinski
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=20260818170926.4019431-1-kuba@kernel.org \
--to=kuba@kernel.org \
--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=peter.hunt@opengear.com \
--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