diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-10-22 15:58:00 -1000 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-10-22 15:58:00 -1000 |
| commit | 734bef548a96e8774d6eef5dfa2026c6edc4e80b (patch) | |
| tree | ce2520b2602ce3b82f9842490d21836f9838d441 /drivers/net/wwan | |
| parent | 864c07de0c7dcbca58b61625dfa2e461ae63b242 (diff) | |
| parent | 1a2c35c49cc7c6be860038b8442b52424a8f123c (diff) | |
| download | linux-next-stable.tar.gz linux-next-stable.zip | |
Merge tag 'net-next-7.4' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-nextstable
Pull networking updates from Paolo Abeni:
"This includes a significant number of low-priority fixes - especially
in the NIC drivers area - that we deferred from the last few RCs in
the previous cycle, which can't be easily outlined in the synopsis
below.
Core & protocols:
- Continue the work on removing rtnl_lock dependencies:
- per-netns ARP and ND neighbour tables, with the default settings
now configurable per netns (inheritance from init_net is
controlled by the new net.core.neigh_inherit_init_net sysctl)
- RCU-protected configuration and rtnl_lock-less fill_info() for
hsr, netkit, sit, vlan and vxlan devices
- per-netns device unregistration for ip tunnel and gre devices
- use the netdev instance lock instead of rtnl_lock when reading
speed and duplex via sysfs
- batch the flushing of uncached routes at device unregistration
time, relieving rtnl_lock contention on netns dismantle.
- Add hardware pacing offload support, admin-controlled via a new
rtnetlink attribute, with sch_fq integration (implemented in idpf).
- Re-segment BIG TCP GSO packets into GSO packets fitting the egress
device limits instead of plain MSS-sized segments, improving
throughput for mixed BIG TCP / non-BIG TCP paths by up to 70%.
- Generate RSS keys that spread flows over all the RX queues; the
previous random keys could leave some queues unused.
- Optimize the bridge VLAN forwarding paths: cache the port-VLAN in
fdb entries and consider only VLAN members when flooding, for ~10%
more Mpps in the fdb forwarding path.
- A few TCP/IP performance improvements aimed at efficient cache
usage and reducing contention.
- Add the TCP ROCCET congestion control, a CUBIC variant aimed at
avoiding bufferbloat in cellular networks.
- Continue the conversion of getsockopt callbacks to sockopt_t.
- Add an ethtool command to list the front-facing ports of an
interface, including SFP cages and modules, using the phy_port
representation.
- Enable BIG TCP support for bareudp tunnels.
- Remove the IPV6_ADDRFORM socket option and disallow buggy TCP state
transitions (listen() + shutdown() + connect() and connect() +
connect(AF_UNSPEC) + listen()).
- Remove most BUG_ON()s from core packet processing, replacing them
with error paths.
Wireless:
- Latency improvements, including lockless station get/dump and
beacon updates, plus RTNL reduction and locking cleanups.
- Infrastructure for non-station TX/RX with MLO.
- NAN (Wi-Fi Aware) Instant Communication support and other NAN
improvements.
- Control frame Integrity Protocol (CIP) and protected TWT support.
- Improvements for AP regulatory checks.
Netfilter:
- Disable br_netfilter in user namespaces and schedule it for removal
in 2027.
- Restrict the raw table in nft_compat to the specific family, hook
and priority.
- Prepare the flowtable for mixed IPv4-over-IPv6 tunneling (and vice
versa).
- Use GFP_KERNEL_ACCOUNT for most netfilter allocations, for better
integration with memcg.
- Allow discovering conntrack zone filtering support in ctnetlink
dumps, and fix nfnetlink_hook handling of large dumps.
Deletions:
- Move the LLC type 2 sockets out of tree. There are no known users
and the code attracts a never-ending stream of LLM-driven fixes.
Device drivers:
- New drivers:
- Meta Platforms NIC (mpnic), the second-generation NIC from Meta
- initial skeleton of the Nebula-matrix NICs driver (nbl)
- X-Powers AC200/AC300 Fast Ethernet PHYs
- Ethernet high-speed NICs:
- Intel:
- i40e: XDP metadata support for RX hash and VLAN tag
- ice: expose the E825 TSPLL state via the DPLL subsystem
- nVidia/Mellanox:
- move the data direct infrastructure from IB to core, in
preparation for netdev support.
- reduce the memory used per offloaded TC rule
- Meta (fbnic):
- support large io_uring zero-copy RX buffers
- Microsoft vNIC (mana):
- add BQL support
- Ethernet NICs consumer, and embedded:
- Airoha:
- LRO offload support
- Cadence (macb):
- context swapping for ringparam and MTU changes, avoiding
buffer loss under memory pressure
- Mucse (rnpgbe):
- TX/RX and link status support
- NXP (enetc):
- SR-IOV improvements and VF support for ENETC v4
- Qualcomm (ppe):
- IPQ5424 support
- Realtek PCI (r8169):
- phylink support
- RSS support for RTL8127
- Renesas (ravb):
- gPTP support for R-Car Gen4
- Synopsys (stmmac):
- XDP multi-buffer support on the TX side
- tc mqprio hw queue priority offload
- support for RK3568 SGMII, Allwinner H616 EMAC1, UltraRISC
DP1000 and Qualcomm sa8255p
- Ethernet switches:
- Mediatek (mt7530):
- fast age and change conduit support
- Microchip (ksz):
- support for KSZ8995XA/KS8995XA
- Microchip (lan966x):
- PCIe FDMA support, ~20x throughput increase in PCIe endpoint
- Microchip (sparx5):
- Time Aware Shaper (taprio) offload
- Motorcomm (yt921x):
- LED and SerDes PCS support
- Realtek (rtl8365mb):
- EEE support
- Ethernet PHYs:
- Broadcom:
- enable jumbo frames on BCM54xx
- Realtek:
- support for RTL8261CE_CG and RTL8261D_VM
- MDIO support for RTL838x and RTL839x
- WiFi drivers:
- Broadcom (brcmfmac):
- FT/OKC roaming offload
- Intel (iwlwifi):
- UHR sniffer support
- support for newer UEFI/BIOS tables
- MediaTek (mt76):
- mt7925 NAN improvements
- offchannel/scan improvements and MLO fixes
- Morse Micro (mm81x):
- monitor mode support
- Qualcomm (ath12k):
- IPQ5332 platform infrastructure
- Realtek (rtw88):
- RTL8723BS support
- SDIO TX performance improvements
- Realtek (rtw89):
- P2P_DEVICE support
- BT-coex updates for RTL8922D
- Bluetooth:
- move H:4 reassembly into the Bluetooth core
- add generic support for vendor packets
- MGMT: add security level changed event
- Bluetooth drivers:
- btusb: support Bulk Serialization Mode
- Intel: support Draco and Titan Lake, allow unsigned firmware
- Mediatek: MT7928 support
- Qualcomm: QCC2072 support
- Broadcom: Synaptics 4384 support"
* tag 'net-next-7.4' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next: (2126 commits)
Bluetooth: btusb: disable by default Qualcomm multi-subsystem
tipc: fix broadcast sender hang on last acker departure
tipc: fix use-after-free of broadcast reception link at node release
r8169: clear TX LPI on RTL8168h at link up after phylink conversion
tun: Require CAP_NET_ADMIN to attach to an ownerless device
net/sched/sch_cake: drop dead nla_nest_end() error check
net/sched/sch_cake: widen buffer_used to u64
net/sched/sch_cake: reduce parent backlog when clearing a tin
net/sched/sch_cake: serialize reconfiguration with the datapath
tg3: Add Datto SIRIS 5 1U Server to restart AER quirk table
net/mlx5e: Stop asserting for RTNL when closing channels
net: shrink the payload-only part of skb dataref to one bit
net: wwan: iosm: validate msg_head before indexing msg_ring
net: bgmac: rate limit RX error messages
selftests: net: veth: test XDP_TX to a peer with GRO enabled
veth: schedule the peer's NAPI after XDP_TX when only GRO is enabled
pppoe: delete session hash entries by pointer match
net: lan969x: don't disable hardirqs in fdma tx clear
net: sparx5/lan969x: do not clobber the PSFP cycle timer update period
net: lan969x: fix DSM calendar for empty taxis
...
Diffstat (limited to 'drivers/net/wwan')
| -rw-r--r-- | drivers/net/wwan/iosm/iosm_ipc_protocol_ops.c | 5 | ||||
| -rw-r--r-- | drivers/net/wwan/mhi_wwan_ctrl.c | 262 | ||||
| -rw-r--r-- | drivers/net/wwan/wwan_core.c | 172 | ||||
| -rw-r--r-- | drivers/net/wwan/wwan_hwsim.c | 4 |
4 files changed, 385 insertions, 58 deletions
diff --git a/drivers/net/wwan/iosm/iosm_ipc_protocol_ops.c b/drivers/net/wwan/iosm/iosm_ipc_protocol_ops.c index f2f86f41fd10..004920068c45 100644 --- a/drivers/net/wwan/iosm/iosm_ipc_protocol_ops.c +++ b/drivers/net/wwan/iosm/iosm_ipc_protocol_ops.c @@ -14,6 +14,11 @@ ipc_protocol_free_msg_get(struct iosm_protocol *ipc_protocol, int *index) u32 new_head = (head + 1) % IPC_MEM_MSG_ENTRIES; union ipc_mem_msg_entry *msg; + if (head >= IPC_MEM_MSG_ENTRIES) { + dev_err(ipc_protocol->dev, "invalid message head %u", head); + return NULL; + } + if (new_head == le32_to_cpu(ipc_protocol->p_ap_shm->msg_tail)) { dev_err(ipc_protocol->dev, "message ring is full"); return NULL; diff --git a/drivers/net/wwan/mhi_wwan_ctrl.c b/drivers/net/wwan/mhi_wwan_ctrl.c index a31d8540fbb8..e3c207588956 100644 --- a/drivers/net/wwan/mhi_wwan_ctrl.c +++ b/drivers/net/wwan/mhi_wwan_ctrl.c @@ -1,8 +1,13 @@ // SPDX-License-Identifier: GPL-2.0-only /* Copyright (c) 2021, Linaro Ltd <loic.poulain@linaro.org> */ #include <linux/kernel.h> +#include <linux/list.h> #include <linux/mhi.h> #include <linux/module.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/termios.h> +#include <linux/workqueue.h> #include <linux/wwan.h> /* MHI wwan flags */ @@ -14,6 +19,32 @@ enum mhi_wwan_flags { #define MHI_WWAN_MAX_MTU 0x8000 +/* IP_CTRL channel message that sets the modem's DTR/RTS control lines */ +struct mhi_dtr_ctrl_msg { + __le32 preamble; + __le32 msg_id; + __le32 dest_id; + __le32 size; + __le32 msg; +} __packed; + +#define MHI_DTR_CTRL_MAGIC 0x4C525443 /* 'CTRL' */ +#define MHI_DTR_MSG_DTR BIT(0) +#define MHI_DTR_MSG_RTS BIT(1) +#define MHI_DTR_HOST_STATE 0x10 + +/* Per-controller IP_CTRL channel, used to signal DTR/RTS to that modem */ +struct mhi_wwan_dtr { + struct mhi_controller *cntrl; + struct mhi_device *mhi_dev; + struct list_head node; + struct mhi_dtr_ctrl_msg *rx_buf; /* DL sink, separate allocation for DMA safety */ + struct delayed_work rx_refill; /* retries a failed sink buffer requeue */ +}; + +static LIST_HEAD(mhi_wwan_dtr_list); +static DEFINE_MUTEX(mhi_wwan_dtr_lock); + struct mhi_wwan_dev { /* Lower level is a mhi dev, upper level is a wwan port */ struct mhi_device *mhi_dev; @@ -35,6 +66,12 @@ struct mhi_wwan_dev { * decremented on data queueing and incremented on data release. */ unsigned int rx_budget; + + /* Latest DTR/RTS state for the modem, sent from dtr_work so a burst of + * changes is coalesced and only the final state has to get through + */ + unsigned int dtr_mdmbits; + struct delayed_work dtr_work; }; /* Increment RX budget and schedule RX refill if necessary */ @@ -103,6 +140,80 @@ static void mhi_wwan_ctrl_refill_work(struct work_struct *work) } } +/* Signal the modem's DTR/RTS lines over its own controller's IP_CTRL channel */ +static int mhi_wwan_ctrl_send_dtr(struct mhi_wwan_dev *mhiwwan, unsigned int mdmbits) +{ + struct mhi_controller *cntrl = mhiwwan->mhi_dev->mhi_cntrl; + struct mhi_device *ctrl_dev = NULL; + struct mhi_dtr_ctrl_msg *dtr_msg; + struct mhi_wwan_dtr *dtr; + u32 msg = 0; + int ret; + + guard(mutex)(&mhi_wwan_dtr_lock); + + list_for_each_entry(dtr, &mhi_wwan_dtr_list, node) { + if (dtr->cntrl == cntrl) { + ctrl_dev = dtr->mhi_dev; + break; + } + } + if (!ctrl_dev) { + dev_dbg(&mhiwwan->mhi_dev->dev, + "IP_CTRL not enumerated; DTR/RTS not signalled to modem\n"); + return 0; + } + + dtr_msg = kzalloc_obj(*dtr_msg); + if (!dtr_msg) + return -ENOMEM; + + if (mdmbits & TIOCM_DTR) + msg |= MHI_DTR_MSG_DTR; + if (mdmbits & TIOCM_RTS) + msg |= MHI_DTR_MSG_RTS; + + dtr_msg->preamble = cpu_to_le32(MHI_DTR_CTRL_MAGIC); + dtr_msg->msg_id = cpu_to_le32(MHI_DTR_HOST_STATE); + dtr_msg->dest_id = cpu_to_le32(mhiwwan->mhi_dev->ul_chan_id); + dtr_msg->size = cpu_to_le32(sizeof(__le32)); + dtr_msg->msg = cpu_to_le32(msg); + + ret = mhi_queue_buf(ctrl_dev, DMA_TO_DEVICE, dtr_msg, sizeof(*dtr_msg), + MHI_EOT); + if (ret) + kfree(dtr_msg); + + return ret; +} + +/* Send the latest DTR/RTS state. A full UL ring or a failed allocation is + * retried shortly, with whatever state is latest by then, so the modem ends + * up with the final state. Other errors mean the channel is going away. + */ +static void mhi_wwan_ctrl_dtr_work(struct work_struct *work) +{ + struct mhi_wwan_dev *mhiwwan = container_of(to_delayed_work(work), + struct mhi_wwan_dev, dtr_work); + int ret; + + ret = mhi_wwan_ctrl_send_dtr(mhiwwan, READ_ONCE(mhiwwan->dtr_mdmbits)); + if (ret == -EAGAIN || ret == -ENOMEM) + schedule_delayed_work(&mhiwwan->dtr_work, msecs_to_jiffies(10)); + else if (ret) + dev_warn_ratelimited(&mhiwwan->mhi_dev->dev, + "failed to send DTR/RTS state to modem: %d\n", + ret); +} + +static void mhi_wwan_ctrl_dtr_rts(struct wwan_port *port, unsigned int mdmbits) +{ + struct mhi_wwan_dev *mhiwwan = wwan_port_get_drvdata(port); + + WRITE_ONCE(mhiwwan->dtr_mdmbits, mdmbits); + schedule_delayed_work(&mhiwwan->dtr_work, 0); +} + static int mhi_wwan_ctrl_start(struct wwan_port *port) { struct mhi_wwan_dev *mhiwwan = wwan_port_get_drvdata(port); @@ -163,6 +274,7 @@ static const struct wwan_port_ops wwan_pops = { .start = mhi_wwan_ctrl_start, .stop = mhi_wwan_ctrl_stop, .tx = mhi_wwan_ctrl_tx, + .dtr_rts = mhi_wwan_ctrl_dtr_rts, }; static void mhi_ul_xfer_cb(struct mhi_device *mhi_dev, @@ -224,6 +336,7 @@ static int mhi_wwan_ctrl_probe(struct mhi_device *mhi_dev, mhiwwan->mhi_dev = mhi_dev; mhiwwan->mtu = MHI_WWAN_MAX_MTU; INIT_WORK(&mhiwwan->rx_refill, mhi_wwan_ctrl_refill_work); + INIT_DELAYED_WORK(&mhiwwan->dtr_work, mhi_wwan_ctrl_dtr_work); spin_lock_init(&mhiwwan->tx_lock); spin_lock_init(&mhiwwan->rx_lock); @@ -252,9 +365,118 @@ static void mhi_wwan_ctrl_remove(struct mhi_device *mhi_dev) struct mhi_wwan_dev *mhiwwan = dev_get_drvdata(&mhi_dev->dev); wwan_remove_port(mhiwwan->wwan_port); + /* Give the DTR/RTS drop from removal one attempt, then stop the work */ + flush_delayed_work(&mhiwwan->dtr_work); + disable_delayed_work_sync(&mhiwwan->dtr_work); kfree(mhiwwan); } +/* IP_CTRL channel driver, bound separately so the data-port path is untouched */ +static void mhi_wwan_dtr_ul_xfer_cb(struct mhi_device *mhi_dev, + struct mhi_result *mhi_result) +{ + /* MHI core has done with the buffer, release it */ + kfree(mhi_result->buf_addr); +} + +/* Requeue the single DL sink buffer so the IP_CTRL DL ring never runs empty. + * This only runs from the work item, so remove() can stop it before the + * channel is torn down. A transient mapping failure is retried, other errors + * mean the channel is going away and the next probe queues a fresh buffer. + */ +static void mhi_wwan_dtr_refill_work(struct work_struct *work) +{ + struct mhi_wwan_dtr *dtr = container_of(to_delayed_work(work), + struct mhi_wwan_dtr, rx_refill); + int ret; + + ret = mhi_queue_buf(dtr->mhi_dev, DMA_FROM_DEVICE, dtr->rx_buf, + sizeof(*dtr->rx_buf), MHI_EOT); + if (ret == -ENOMEM) { + dev_warn_ratelimited(&dtr->mhi_dev->dev, + "failed to requeue IP_CTRL RX buffer, retrying\n"); + schedule_delayed_work(&dtr->rx_refill, msecs_to_jiffies(100)); + } else if (ret) { + dev_dbg(&dtr->mhi_dev->dev, + "failed to requeue IP_CTRL RX buffer: %d\n", ret); + } +} + +static void mhi_wwan_dtr_dl_xfer_cb(struct mhi_device *mhi_dev, + struct mhi_result *mhi_result) +{ + struct mhi_wwan_dtr *dtr = dev_get_drvdata(&mhi_dev->dev); + + /* Channel is being torn down (e.g. -ENOTCONN), do not requeue */ + if (mhi_result->transaction_status && + mhi_result->transaction_status != -EOVERFLOW) + return; + + /* Modem serial state not needed, requeue the sink buffer to keep DL ring live */ + schedule_delayed_work(&dtr->rx_refill, 0); +} + +static int mhi_wwan_dtr_probe(struct mhi_device *mhi_dev, + const struct mhi_device_id *id) +{ + struct mhi_wwan_dtr *dtr; + int ret; + + dtr = kzalloc_obj(*dtr); + if (!dtr) + return -ENOMEM; + + dtr->rx_buf = kmalloc_obj(*dtr->rx_buf); + if (!dtr->rx_buf) { + ret = -ENOMEM; + goto err_free_dtr; + } + INIT_DELAYED_WORK(&dtr->rx_refill, mhi_wwan_dtr_refill_work); + + ret = mhi_prepare_for_transfer(mhi_dev); + if (ret) + goto err_free_buf; + + dtr->cntrl = mhi_dev->mhi_cntrl; + dtr->mhi_dev = mhi_dev; + dev_set_drvdata(&mhi_dev->dev, dtr); + + /* On failure mhi_probe() unprepares the channels itself */ + ret = mhi_queue_buf(mhi_dev, DMA_FROM_DEVICE, dtr->rx_buf, + sizeof(*dtr->rx_buf), MHI_EOT); + if (ret) + goto err_free_buf; + + mutex_lock(&mhi_wwan_dtr_lock); + list_add(&dtr->node, &mhi_wwan_dtr_list); + mutex_unlock(&mhi_wwan_dtr_lock); + + return 0; + +err_free_buf: + kfree(dtr->rx_buf); +err_free_dtr: + kfree(dtr); + return ret; +} + +static void mhi_wwan_dtr_remove(struct mhi_device *mhi_dev) +{ + struct mhi_wwan_dtr *dtr = dev_get_drvdata(&mhi_dev->dev); + + mutex_lock(&mhi_wwan_dtr_lock); + list_del(&dtr->node); + mutex_unlock(&mhi_wwan_dtr_lock); + + /* Stop the refill work, and stop a late DL callback re-arming it, before + * the channel is torn down so no requeue can race with the teardown + */ + disable_delayed_work_sync(&dtr->rx_refill); + mhi_unprepare_from_transfer(mhi_dev); + kfree(dtr->rx_buf); + kfree(dtr); +} + static const struct mhi_device_id mhi_wwan_ctrl_match_table[] = { { .chan = "DUN", .driver_data = WWAN_PORT_AT }, { .chan = "DUN2", .driver_data = WWAN_PORT_AT }, @@ -278,7 +500,45 @@ static struct mhi_driver mhi_wwan_ctrl_driver = { }, }; -module_mhi_driver(mhi_wwan_ctrl_driver); +static const struct mhi_device_id mhi_wwan_dtr_match_table[] = { + { .chan = "IP_CTRL" }, + {}, +}; +MODULE_DEVICE_TABLE(mhi, mhi_wwan_dtr_match_table); + +static struct mhi_driver mhi_wwan_dtr_driver = { + .id_table = mhi_wwan_dtr_match_table, + .remove = mhi_wwan_dtr_remove, + .probe = mhi_wwan_dtr_probe, + .ul_xfer_cb = mhi_wwan_dtr_ul_xfer_cb, + .dl_xfer_cb = mhi_wwan_dtr_dl_xfer_cb, + .driver = { + .name = "mhi_wwan_dtr", + }, +}; + +static int __init mhi_wwan_ctrl_init(void) +{ + int ret; + + ret = mhi_driver_register(&mhi_wwan_dtr_driver); + if (ret) + return ret; + + ret = mhi_driver_register(&mhi_wwan_ctrl_driver); + if (ret) + mhi_driver_unregister(&mhi_wwan_dtr_driver); + + return ret; +} +module_init(mhi_wwan_ctrl_init); + +static void __exit mhi_wwan_ctrl_exit(void) +{ + mhi_driver_unregister(&mhi_wwan_ctrl_driver); + mhi_driver_unregister(&mhi_wwan_dtr_driver); +} +module_exit(mhi_wwan_ctrl_exit); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("MHI WWAN CTRL Driver"); diff --git a/drivers/net/wwan/wwan_core.c b/drivers/net/wwan/wwan_core.c index ffbcf11e4e68..7097b58dafde 100644 --- a/drivers/net/wwan/wwan_core.c +++ b/drivers/net/wwan/wwan_core.c @@ -25,6 +25,10 @@ #include <net/rtnetlink.h> #include <uapi/linux/wwan.h> +/* Modem control bits userspace may set on an AT port, as for tty_tiocmset() */ +#define WWAN_TIOCM_OUTPUTS (TIOCM_DTR | TIOCM_RTS | TIOCM_OUT1 | \ + TIOCM_OUT2 | TIOCM_LOOP) + /* Maximum number of minors in use */ #define WWAN_MAX_MINORS (1 << MINORBITS) @@ -73,11 +77,10 @@ struct wwan_device { * @start_count: Port start counter * @flags: Store port state and capabilities * @ops: Pointer to WWAN port operations - * @ops_lock: Protect port ops + * @ops_lock: Protect port ops, and the AT/QCDM termios and TIOCM state * @dev: Underlying device * @rxq: Buffer inbound queue * @waitqueue: The waitqueue for port fops (read/write/poll) - * @data_lock: Port specific data access serialization * @headroom_len: SKB reserved headroom size * @frag_len: Length to fragment packet * @at_data: AT port specific data @@ -88,11 +91,10 @@ struct wwan_port { unsigned int start_count; unsigned long flags; const struct wwan_port_ops *ops; - struct mutex ops_lock; /* Serialize ops + protect against removal */ + struct mutex ops_lock; /* Serialize ops + at_data, protect against removal */ struct device dev; struct sk_buff_head rxq; wait_queue_head_t waitqueue; - struct mutex data_lock; /* Port specific data access serialization */ size_t headroom_len; size_t frag_len; union { @@ -374,7 +376,6 @@ static void wwan_port_destroy(struct device *dev) if (dev->class == &wwan_class) ida_free(&minors, MINOR(dev->devt)); - mutex_destroy(&port->data_lock); mutex_destroy(&port->ops_lock); kfree(port); } @@ -653,7 +654,10 @@ struct wwan_port *wwan_create_port(struct device *parent, mutex_init(&port->ops_lock); skb_queue_head_init(&port->rxq); init_waitqueue_head(&port->waitqueue); - mutex_init(&port->data_lock); + + /* AT ports default to HUPCL, so DTR/RTS are dropped on last close */ + if (type == WWAN_PORT_AT) + port->at_data.termios.c_cflag = HUPCL; port->dev.parent = &wwandev->dev; port->dev.type = &wwan_port_dev_type; @@ -679,12 +683,44 @@ error_wwandev_remove: } EXPORT_SYMBOL_GPL(wwan_create_port); +/* Raise DTR/RTS when an AT port is first opened, as a TTY does on open. + * Unlike a TTY the termios baud rate is not used. Called with ops_lock held. + */ +static void wwan_port_raise_dtr_rts(struct wwan_port *port) +{ + lockdep_assert_held(&port->ops_lock); + + if (port->type != WWAN_PORT_AT || !port->ops->dtr_rts) + return; + + port->at_data.mdmbits |= TIOCM_DTR | TIOCM_RTS; + port->ops->dtr_rts(port, port->at_data.mdmbits); +} + +/* Drop DTR/RTS on an AT port. Called with ops_lock held. On last close + * the lines are only dropped if HUPCL is set, as for a TTY. When a port is + * removed while still open they are always dropped. + */ +static void wwan_port_drop_dtr_rts(struct wwan_port *port, bool hupcl_only) +{ + lockdep_assert_held(&port->ops_lock); + + if (port->type != WWAN_PORT_AT || !port->ops->dtr_rts) + return; + if (hupcl_only && !(port->at_data.termios.c_cflag & HUPCL)) + return; + + port->at_data.mdmbits &= ~(TIOCM_DTR | TIOCM_RTS); + port->ops->dtr_rts(port, port->at_data.mdmbits); +} + void wwan_remove_port(struct wwan_port *port) { struct wwan_device *wwandev = to_wwan_dev(port->dev.parent); mutex_lock(&port->ops_lock); if (port->start_count) { + wwan_port_drop_dtr_rts(port, false); port->ops->stop(port); port->start_count = 0; } @@ -756,8 +792,11 @@ static int wwan_port_op_start(struct wwan_port *port) } /* If port is already started, don't start again */ - if (!port->start_count) + if (!port->start_count) { ret = port->ops->start(port); + if (!ret) + wwan_port_raise_dtr_rts(port); + } if (!ret) port->start_count++; @@ -773,8 +812,10 @@ static void wwan_port_op_stop(struct wwan_port *port) mutex_lock(&port->ops_lock); port->start_count--; if (!port->start_count) { - if (port->ops) + if (port->ops) { + wwan_port_drop_dtr_rts(port, true); port->ops->stop(port); + } skb_queue_purge(&port->rxq); clear_bit(WWAN_PORT_EXCLUSIVE, &port->flags); } @@ -976,92 +1017,109 @@ static __poll_t wwan_port_fops_poll(struct file *filp, poll_table *wait) } /* Implements minimalistic stub terminal IOCTLs support */ -static long wwan_port_fops_at_ioctl(struct wwan_port *port, unsigned int cmd, - unsigned long arg) +/* TCGETS/TCSETS and their termios2 variants. @size is the size of the user + * structure, a prefix of struct ktermios. As for the TIOCM ioctls, user + * memory is only accessed outside ops_lock. + */ +static long wwan_port_termios_ioctl(struct wwan_port *port, bool set, + void __user *uarg, size_t size) { - int ret = 0; + struct ktermios termios; - mutex_lock(&port->data_lock); + if (!set) { + scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &port->ops_lock) + termios = port->at_data.termios; + return copy_to_user(uarg, &termios, size) ? -EFAULT : 0; + } + + /* Commit only on success, a partial copy must not clear HUPCL */ + if (copy_from_user(&termios, uarg, size)) + return -EFAULT; + scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &port->ops_lock) + memcpy(&port->at_data.termios, &termios, size); + return 0; +} +static long wwan_port_fops_at_ioctl(struct wwan_port *port, unsigned int cmd, + unsigned long arg) +{ + /* ops_lock serialises the termios/TIOCM state with port ops and + * removal. It is only held while that state is read or updated, never + * across a user copy, and is taken interruptibly as a blocking write + * holds it too. + */ switch (cmd) { case TCFLSH: - break; + return 0; case TCGETS: - if (copy_to_user((void __user *)arg, &port->at_data.termios, - sizeof(struct termios))) - ret = -EFAULT; - break; - case TCSETS: case TCSETSW: case TCSETSF: - if (copy_from_user(&port->at_data.termios, (void __user *)arg, - sizeof(struct termios))) - ret = -EFAULT; - break; + return wwan_port_termios_ioctl(port, cmd != TCGETS, + (void __user *)arg, + sizeof(struct termios)); #ifdef TCGETS2 case TCGETS2: - if (copy_to_user((void __user *)arg, &port->at_data.termios, - sizeof(struct termios2))) - ret = -EFAULT; - break; - case TCSETS2: case TCSETSW2: case TCSETSF2: - if (copy_from_user(&port->at_data.termios, (void __user *)arg, - sizeof(struct termios2))) - ret = -EFAULT; - break; + return wwan_port_termios_ioctl(port, cmd != TCGETS2, + (void __user *)arg, + sizeof(struct termios2)); #endif - case TIOCMGET: - ret = put_user(port->at_data.mdmbits, (int __user *)arg); - break; + case TIOCMGET: { + int mdmbits; + + scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &port->ops_lock) + mdmbits = port->at_data.mdmbits; + return put_user(mdmbits, (int __user *)arg); + } case TIOCMSET: case TIOCMBIC: case TIOCMBIS: { int mdmbits; - if (copy_from_user(&mdmbits, (int __user *)arg, sizeof(int))) { - ret = -EFAULT; - break; + if (get_user(mdmbits, (int __user *)arg)) + return -EFAULT; + mdmbits &= WWAN_TIOCM_OUTPUTS; + + scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &port->ops_lock) { + int old = port->at_data.mdmbits; + + if (cmd == TIOCMBIC) + port->at_data.mdmbits &= ~mdmbits; + else if (cmd == TIOCMBIS) + port->at_data.mdmbits |= mdmbits; + else + port->at_data.mdmbits = mdmbits; + /* Only signal the driver when DTR or RTS actually changes */ + if (port->type == WWAN_PORT_AT && port->ops && + port->ops->dtr_rts && + (old ^ port->at_data.mdmbits) & (TIOCM_DTR | TIOCM_RTS)) + port->ops->dtr_rts(port, port->at_data.mdmbits); } - if (cmd == TIOCMBIC) - port->at_data.mdmbits &= ~mdmbits; - else if (cmd == TIOCMBIS) - port->at_data.mdmbits |= mdmbits; - else - port->at_data.mdmbits = mdmbits; - break; + return 0; } case TIOCEXCL: set_bit(WWAN_PORT_EXCLUSIVE, &port->flags); - break; + return 0; case TIOCNXCL: clear_bit(WWAN_PORT_EXCLUSIVE, &port->flags); - break; + return 0; case TIOCGEXCL: - { - int excl = test_bit(WWAN_PORT_EXCLUSIVE, &port->flags); - - ret = put_user(excl, (int __user *)arg); - break; - } + return put_user(test_bit(WWAN_PORT_EXCLUSIVE, &port->flags), + (int __user *)arg); default: - ret = -ENOIOCTLCMD; + return -ENOIOCTLCMD; } - - mutex_unlock(&port->data_lock); - - return ret; } static long wwan_port_fops_ioctl(struct file *filp, unsigned int cmd, diff --git a/drivers/net/wwan/wwan_hwsim.c b/drivers/net/wwan/wwan_hwsim.c index c3c2d2a68767..34eea3edc824 100644 --- a/drivers/net/wwan/wwan_hwsim.c +++ b/drivers/net/wwan/wwan_hwsim.c @@ -500,6 +500,7 @@ static ssize_t wwan_hwsim_debugfs_portdestroy_write(struct file *file, } static const struct file_operations wwan_hwsim_debugfs_portdestroy_fops = { + .owner = THIS_MODULE, .write = wwan_hwsim_debugfs_portdestroy_write, .open = simple_open, .llseek = noop_llseek, @@ -524,6 +525,7 @@ static ssize_t wwan_hwsim_debugfs_portcreate_write(struct file *file, } static const struct file_operations wwan_hwsim_debugfs_portcreate_fops = { + .owner = THIS_MODULE, .write = wwan_hwsim_debugfs_portcreate_write, .open = simple_open, .llseek = noop_llseek, @@ -545,6 +547,7 @@ static ssize_t wwan_hwsim_debugfs_devdestroy_write(struct file *file, } static const struct file_operations wwan_hwsim_debugfs_devdestroy_fops = { + .owner = THIS_MODULE, .write = wwan_hwsim_debugfs_devdestroy_write, .open = simple_open, .llseek = noop_llseek, @@ -568,6 +571,7 @@ static ssize_t wwan_hwsim_debugfs_devcreate_write(struct file *file, } static const struct file_operations wwan_hwsim_debugfs_devcreate_fops = { + .owner = THIS_MODULE, .write = wwan_hwsim_debugfs_devcreate_write, .open = simple_open, .llseek = noop_llseek, |
