* [PATCH net] net: mctp i3c: clean up notifier and buses if driver register fails
From: Myeonghun Pak @ 2026-07-14 8:10 UTC (permalink / raw)
To: Jeremy Kerr, Matt Johnston
Cc: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, netdev, linux-kernel, Myeonghun Pak, Ijae Kim
mctp_i3c_mod_init() registers the I3C bus notifier and then walks the
existing buses with i3c_for_each_bus_locked(mctp_i3c_bus_add_new, NULL)
before registering the I3C device driver. If i3c_driver_register()
fails, the function returns the error directly, leaving the notifier
registered and every mctp_i3c_bus object created for the existing buses
allocated. The notifier is left pointing into the module that failed to
load and the bus list is leaked.
Mirror the module exit path on this failure: unregister the notifier and
tear down the buses that were added before returning the error.
Fixes: c8755b29b58e ("mctp i3c: MCTP I3C driver")
Co-developed-by: Ijae Kim <ae878000@gmail.com>
Signed-off-by: Ijae Kim <ae878000@gmail.com>
Signed-off-by: Myeonghun Pak <mhun512@gmail.com>
---
drivers/net/mctp/mctp-i3c.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/drivers/net/mctp/mctp-i3c.c b/drivers/net/mctp/mctp-i3c.c
index 6d2bbae747..677e77e917 100644
--- a/drivers/net/mctp/mctp-i3c.c
+++ b/drivers/net/mctp/mctp-i3c.c
@@ -740,9 +740,14 @@ static __init int mctp_i3c_mod_init(void)
rc = i3c_driver_register(&mctp_i3c_driver);
if (rc < 0)
- return rc;
+ goto err_unregister_notifier;
return 0;
+
+err_unregister_notifier:
+ i3c_unregister_notifier(&mctp_i3c_notifier);
+ mctp_i3c_bus_remove_all();
+ return rc;
}
static __exit void mctp_i3c_mod_exit(void)
--
2.53.0
^ permalink raw reply related
* [PATCH net] net: hsr: clean up the slave VLAN filters when deleting a port
From: Jiayuan Chen @ 2026-07-14 8:06 UTC (permalink / raw)
To: netdev
Cc: Jiayuan Chen, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Kees Cook, Łukasz Majewski,
Fernando Fernandez Mancera, Hangbin Liu, Jakub Acs,
Xiaoliang Yang, MD Danish Anwar, Murali Karicheri, Jiri Pirko,
linux-kernel
Our internal syzkaller reported this warning:
netdevsim netdevsim10 eth0: entered promiscuous mode
netdevsim netdevsim10 eth1: entered promiscuous mode
netdevsim netdevsim10 eth1 (unregistering): left promiscuous mode
------------[ cut here ]------------
WARNING: drivers/net/netdevsim/netdev.c:1208 at nsim_destroy+0x276/0x6e0, CPU#3: 1/46
Modules linked in:
CPU: 3 UID: 0 PID: 46 Comm: kworker/u16:1 Not tainted 7.2.0-rc2+ #286 PREEMPT
Hardware name: QEMU Ubuntu 24.04 PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
Workqueue: netns cleanup_net
RIP: 0010:nsim_destroy (drivers/net/netdevsim/netdev.c:1031 drivers/net/netdevsim/netdev.c:1201)
RSP: 0018:ffffc90000337898 EFLAGS: 00010293
RAX: 0000000000000001 RBX: ffff8881036c8b00 RCX: 0000000000000000
RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000000000000000
RBP: ffffc900003378f0 R08: 0000000000000000 R09: 0000000000000000
R10: ffffffffad4186a8 R11: ffffffffad41c3db R12: ffff8881036c9268
R13: dffffc0000000000 R14: ffffed10206d9164 R15: 0000000000000001
FS: 0000000000000000(0000) GS:ffff888160866000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 000055bcf1a2e0c4 CR3: 0000000105376003 CR4: 0000000000770ef0
PKRU: 55555554
Call Trace:
<TASK>
__nsim_dev_port_del (drivers/net/netdevsim/dev.c:1547)
nsim_dev_reload_destroy (drivers/net/netdevsim/dev.c:1561 drivers/net/netdevsim/dev.c:1785)
nsim_dev_reload_down (drivers/net/netdevsim/dev.c:1038)
devlink_reload (net/devlink/dev.c:462)
devlink_pernet_pre_exit (net/devlink/core.c:578)
ops_undo_list (net/core/net_namespace.c:161 net/core/net_namespace.c:235)
cleanup_net (net/core/net_namespace.c:706)
process_one_work (kernel/workqueue.c:3322)
worker_thread (kernel/workqueue.c:3405 kernel/workqueue.c:3486)
kthread (kernel/kthread.c:436)
ret_from_fork (arch/x86/kernel/process.c:158)
ret_from_fork_asm (arch/x86/entry/entry_64.S:245)
</TASK>
---[ end trace 0000000000000000 ]---
netdevsim netdevsim10 eth0 (unregistering): left promiscuous mode
vlan_vids_add_by_dev() is what passes the VID filter info down to the real
device. A virtual device does no filtering itself, so it has to push the
VIDs down to its slaves. HSR already does this correctly on the add side:
hsr_ndo_vlan_rx_add_vid() calls vlan_vid_add() on each slave.
We just never clean the slaves up on delete. hsr_del_port() drops the port
and leaves the VIDs sitting on the slave, so the filter entries leak.
netdevsim keeps a bitmap of the VIDs it was told to filter and checks that
it is empty when the netdev goes away, which is what trips the warning
above.
So do the cleanup in hsr_del_port(). We follow hsr_ndo_vlan_rx_add_vid()
here: it only pushes the VIDs to HSR_PT_SLAVE_A and HSR_PT_SLAVE_B, so the
del side only touches those two as well.
Reproducer:
ip netns add ns0
ip netns exec ns0 sh -c 'echo "10 2" > /sys/bus/netdevsim/new_device'
ip netns exec ns0 ip link add hsr0 type hsr slave1 eth0 slave2 eth1
ip netns exec ns0 ip link add link hsr0 name hsr0.1 type vlan id 1
ip netns del ns0
Fixes: 1a8a63a5305e ("net: hsr: Add VLAN CTAG filter support")
Signed-off-by: Jiayuan Chen <jiayuan.chen@linux.dev>
---
net/hsr/hsr_slave.c | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/net/hsr/hsr_slave.c b/net/hsr/hsr_slave.c
index d9af9e65f72f..b1baefb65c7a 100644
--- a/net/hsr/hsr_slave.c
+++ b/net/hsr/hsr_slave.c
@@ -237,6 +237,13 @@ void hsr_del_port(struct hsr_port *port)
list_del_rcu(&port->port_list);
if (port != master) {
+ /* Drop the VLAN ids that hsr_ndo_vlan_rx_add_vid() pushed down
+ * to this slave. Only slave ports get them, see there.
+ */
+ if (port->type == HSR_PT_SLAVE_A ||
+ port->type == HSR_PT_SLAVE_B)
+ vlan_vids_del_by_dev(port->dev, master->dev);
+
netdev_update_features(master->dev);
dev_set_mtu(master->dev, hsr_get_max_mtu(hsr));
netdev_rx_handler_unregister(port->dev);
--
2.43.0
^ permalink raw reply related
* Re: [PATCH net v3] net: stmmac: intel: skip SerDes reconfig when rate is unchanged
From: Maxime Chevallier @ 2026-07-14 8:05 UTC (permalink / raw)
To: Markus Breitenberger, netdev
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, mcoquelin.stm32,
alexandre.torgue, rmk+kernel, yong.liang.choong, linux-stm32,
linux-arm-kernel, stable, Markus Breitenberger
In-Reply-To: <20260713171619.192452-1-bre@breiti.cc>
Hi Markus,
On 7/13/26 19:16, Markus Breitenberger wrote:
> From: Markus Breitenberger <bre@keba.com>
>
> intel_mac_finish() is registered as the phylink mac_finish()
> callback for the Elkhart Lake SGMII ports. phylink calls it at
> the end of every major link reconfiguration, including the
> initial one during probe.
>
> The callback selects the PMC ModPHY LCPLL programming for the
> requested MAC-side interface and then power-cycles the SerDes.
> On Elkhart Lake that ModPHY is also used by the on-die AHCI
> SATA PHY. Reapplying the programming during the initial
> boot-time link-up disturbs the shared analog block while it is
> still driving SATA, so the SATA link fails to train:
>
> ata1: SATA link down (SStatus 1 SControl 300)
>
> The disk carrying the root filesystem is never detected and the
> system hangs at rootwait. Ethernet itself comes up normally,
> which makes the failure look unrelated to the network driver.
>
> Before mac_finish() runs, the legacy SerDes power-up path has
> already programmed SERDES_GCR0 for the current interface. The
> 1G and 2.5G ModPHY tables selected by mac_finish() correspond
> to the SerDes lane rate, so read that rate back from SERDES_GCR0
> and skip the PMC reprogramming and SerDes power-cycle when it
> already matches the selected interface.
>
> This keeps the disruptive reprogramming out of the boot path
> when the SerDes is configured correctly, while preserving the
> previous behavior when a real SGMII/1000BASE-X to 2500BASE-X
> rate change is needed. If the register read fails, reconfigure
> as before.
>
> Fixes: a42f6b3f1cc1 ("net: stmmac: configure SerDes according to the interface mode")
> Cc: stable@vger.kernel.org
> Assisted-by: GitHub-Copilot:claude-opus-4.8
> Signed-off-by: Markus Breitenberger <bre@keba.com>
Reviewed-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
Maxime
^ permalink raw reply
* Re: [PATCH net-next v3 1/3] net/smc: clear the correct v2 slot and buffer in smc_wr_tx_put_slot()
From: Dust Li @ 2026-07-14 8:05 UTC (permalink / raw)
To: D. Wythe, mjambigi, wenjia, wintera, tonylu, guwen
Cc: kuba, davem, netdev, linux-s390, linux-rdma, pabeni, edumazet,
sidraya, jaka
In-Reply-To: <20260710033356.16460-2-alibuda@linux.alibaba.com>
On 2026-07-10 11:33:54, D. Wythe wrote:
>smc_wr_tx_put_slot() tries to reset the v2 pending slot and buffer with
>memset(&link->wr_tx_v2_pend, 0, sizeof(link->wr_tx_v2_pend)) and the
>equivalent for wr_tx_buf_v2. Both are pointers, so this zeroes the 8-byte
>pointer variable instead of the structure it points to. The pending slot
>and buffer are therefore never actually cleared, and the pointers get
>overwritten with NULL.
>
>Pass the pointers directly and use sizeof(*pointer) so the intended
>structures are cleared.
>
>Fixes: 8799e310fb3f ("net/smc: add v2 support to the work request layer")
>Signed-off-by: D. Wythe <alibuda@linux.alibaba.com>
Reviewed-by: Dust Li <dust.li@linux.alibaba.com>
Best regards,
Dust
>---
> net/smc/smc_wr.c | 8 ++++----
> 1 file changed, 4 insertions(+), 4 deletions(-)
>
>diff --git a/net/smc/smc_wr.c b/net/smc/smc_wr.c
>index 59c92b46945c..6b5add922993 100644
>--- a/net/smc/smc_wr.c
>+++ b/net/smc/smc_wr.c
>@@ -288,10 +288,10 @@ int smc_wr_tx_put_slot(struct smc_link *link,
> } else if (link->lgr->smc_version == SMC_V2 &&
> pend->idx == link->wr_tx_cnt) {
> /* Large v2 buffer */
>- memset(&link->wr_tx_v2_pend, 0,
>- sizeof(link->wr_tx_v2_pend));
>- memset(&link->lgr->wr_tx_buf_v2, 0,
>- sizeof(link->lgr->wr_tx_buf_v2));
>+ memset(link->wr_tx_v2_pend, 0,
>+ sizeof(*link->wr_tx_v2_pend));
>+ memset(link->lgr->wr_tx_buf_v2, 0,
>+ sizeof(*link->lgr->wr_tx_buf_v2));
> return 1;
> }
>
>--
>2.45.0
^ permalink raw reply
* [PATCH v2] RDMA/core: quiesce CQ polling before device shutdown on reboot
From: Chenguang Zhao @ 2026-07-14 7:55 UTC (permalink / raw)
To: leon, jgg, saeedm, tariqt, mbloch, davem, edumazet, kuba, pabeni
Cc: linux-rdma, netdev, chenguang.zhao, Chenguang Zhao
In-Reply-To: <20260702073422.279820-1-chenguang.zhao@linux.dev>
From: Chenguang Zhao <zhaochenguang@kylinos.cn>
On reboot -f with NFS over RDMA, mlx5 shutdown can tear the device
down while ib-comp-wq still polls live CQs, leading to UAF in
wr_cqe->done().
Mark the device shutting down before teardown, and skip CQ poll/arm
and SYS_ERROR completion delivery while that flag is set.
Signed-off-by: Chenguang Zhao <zhaochenguang@kylinos.cn>
---
1. Set the SHUTTING_DOWN flag at the mlx5 PCI/SF shutdown entry.
2. Skip SYS_ERROR notifications during shutdown to avoid the
internal error path re-arming CQs.
3. Make mlx5_ib_poll_cq / mlx5_ib_arm_cq return immediately while
that flag is set, so completions are no longer delivered.
drivers/infiniband/hw/mlx5/cq.c | 6 ++++++
drivers/infiniband/hw/mlx5/main.c | 11 +++++++++++
drivers/net/ethernet/mellanox/mlx5/core/health.c | 3 +++
drivers/net/ethernet/mellanox/mlx5/core/main.c | 1 +
.../net/ethernet/mellanox/mlx5/core/sf/dev/driver.c | 1 +
include/linux/mlx5/driver.h | 6 ++++++
6 files changed, 28 insertions(+)
diff --git a/drivers/infiniband/hw/mlx5/cq.c b/drivers/infiniband/hw/mlx5/cq.c
index 49b4bf148a4a..584445e6d2fc 100644
--- a/drivers/infiniband/hw/mlx5/cq.c
+++ b/drivers/infiniband/hw/mlx5/cq.c
@@ -618,6 +618,9 @@ int mlx5_ib_poll_cq(struct ib_cq *ibcq, int num_entries, struct ib_wc *wc)
int soft_polled = 0;
int npolled;
+ if (mlx5_core_is_shutting_down(mdev))
+ return 0;
+
spin_lock_irqsave(&cq->lock, flags);
if (mdev->state == MLX5_DEVICE_STATE_INTERNAL_ERROR) {
/* make sure no soft wqe's are waiting */
@@ -653,6 +656,9 @@ int mlx5_ib_arm_cq(struct ib_cq *ibcq, enum ib_cq_notify_flags flags)
unsigned long irq_flags;
int ret = 0;
+ if (mlx5_core_is_shutting_down(mdev))
+ return 0;
+
spin_lock_irqsave(&cq->lock, irq_flags);
if (cq->notify_flags != IB_CQ_NEXT_COMP)
cq->notify_flags = flags & IB_CQ_SOLICITED_MASK;
diff --git a/drivers/infiniband/hw/mlx5/main.c b/drivers/infiniband/hw/mlx5/main.c
index 02809114fc79..265c95129fad 100644
--- a/drivers/infiniband/hw/mlx5/main.c
+++ b/drivers/infiniband/hw/mlx5/main.c
@@ -2976,6 +2976,9 @@ static void mlx5_ib_handle_internal_error(struct mlx5_ib_dev *ibdev)
unsigned long flags_cq;
unsigned long flags;
+ if (mlx5_core_is_shutting_down(ibdev->mdev))
+ return;
+
INIT_LIST_HEAD(&cq_armed_list);
/* Go over qp list reside on that ibdev, sync with create/destroy qp.*/
@@ -3200,6 +3203,9 @@ static void mlx5_ib_handle_sys_error_event(struct work_struct *_work)
struct mlx5_ib_dev *ibdev = work->dev;
struct ib_event ibev;
+ if (mlx5_core_is_shutting_down(ibdev->mdev))
+ goto out;
+
ibev.event = IB_EVENT_DEVICE_FATAL;
mlx5_ib_handle_internal_error(ibdev);
ibev.element.port_num = (u8)(unsigned long)work->param;
@@ -3222,10 +3228,15 @@ static int mlx5_ib_sys_error_event(struct notifier_block *nb,
unsigned long event, void *param)
{
struct mlx5_ib_event_work *work;
+ struct mlx5_ib_dev *ibdev;
if (event != MLX5_DEV_EVENT_SYS_ERROR)
return NOTIFY_DONE;
+ ibdev = container_of(nb, struct mlx5_ib_dev, sys_error_events);
+ if (mlx5_core_is_shutting_down(ibdev->mdev))
+ return NOTIFY_OK;
+
work = kmalloc_obj(*work, GFP_ATOMIC);
if (!work)
return NOTIFY_DONE;
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/health.c b/drivers/net/ethernet/mellanox/mlx5/core/health.c
index aeeb136f5ebc..d9cc42c4c310 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/health.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/health.c
@@ -202,6 +202,9 @@ static void enter_error_state(struct mlx5_core_dev *dev, bool force)
mlx5_cmd_flush(dev);
}
+ if (mlx5_core_is_shutting_down(dev))
+ return;
+
mlx5_notifier_call_chain(dev->priv.events, MLX5_DEV_EVENT_SYS_ERROR, (void *)1);
}
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/main.c b/drivers/net/ethernet/mellanox/mlx5/core/main.c
index 643b4aac2033..078114bfb357 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/main.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/main.c
@@ -2192,6 +2192,7 @@ static void shutdown(struct pci_dev *pdev)
int err;
mlx5_core_info(dev, "Shutdown was called\n");
+ set_bit(MLX5_INTERFACE_STATE_SHUTTING_DOWN, &dev->intf_state);
set_bit(MLX5_BREAK_FW_WAIT, &dev->intf_state);
mlx5_drain_fw_reset(dev);
mlx5_drain_health_wq(dev);
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/sf/dev/driver.c b/drivers/net/ethernet/mellanox/mlx5/core/sf/dev/driver.c
index 4391ef0bab5d..6f8242bfb455 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/sf/dev/driver.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/sf/dev/driver.c
@@ -111,6 +111,7 @@ static void mlx5_sf_dev_shutdown(struct auxiliary_device *adev)
struct mlx5_sf_dev *sf_dev = container_of(adev, struct mlx5_sf_dev, adev);
struct mlx5_core_dev *mdev = sf_dev->mdev;
+ set_bit(MLX5_INTERFACE_STATE_SHUTTING_DOWN, &mdev->intf_state);
set_bit(MLX5_BREAK_FW_WAIT, &mdev->intf_state);
mlx5_drain_health_wq(mdev);
mlx5_unload_one(mdev, false);
diff --git a/include/linux/mlx5/driver.h b/include/linux/mlx5/driver.h
index b1871c0821d0..a9efc37cd254 100644
--- a/include/linux/mlx5/driver.h
+++ b/include/linux/mlx5/driver.h
@@ -653,8 +653,14 @@ enum mlx5_device_state {
enum mlx5_interface_state {
MLX5_INTERFACE_STATE_UP = BIT(0),
MLX5_BREAK_FW_WAIT = BIT(1),
+ MLX5_INTERFACE_STATE_SHUTTING_DOWN = BIT(2),
};
+static inline bool mlx5_core_is_shutting_down(struct mlx5_core_dev *dev)
+{
+ return test_bit(MLX5_INTERFACE_STATE_SHUTTING_DOWN, &dev->intf_state);
+}
+
enum mlx5_pci_status {
MLX5_PCI_STATUS_DISABLED,
MLX5_PCI_STATUS_ENABLED,
--
2.25.1
^ permalink raw reply related
* RE: [PATCH 2/2] net: wwan: qcom_bam_dmux: Alloc RX buffers as a single coherent block
From: Jagielski, Jedrzej @ 2026-07-14 7:55 UTC (permalink / raw)
To: Vishnu Santhosh, Stephan Gerhold, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Loic Poulain, Sergey Ryazanov,
Johannes Berg
Cc: linux-arm-msm@vger.kernel.org, netdev@vger.kernel.org,
devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
chris.lew@oss.qualcomm.com, Deepak Kumar Singh
In-Reply-To: <20260714-qcom-bam-dmux-vmid-ext-v1-2-3f29da7cca76@oss.qualcomm.com>
From: Vishnu Santhosh <vishnu.santhosh@oss.qualcomm.com>
Sent: Tuesday, July 14, 2026 7:33 AM
>On Qualcomm SoCs where the modem (e.g. the mDSP on Shikra, VMID 43 /
>NAV) is the AXI master for BAM-DMUX RX transfers and the XPU enforces
>per-region access control, each individually DMA-mapped RX buffer
>requires its own XPU resource group (RG). With ~16 RGs available, the
>32 per-buffer dma_map_single() calls exhaust the table and the first
>inbound transfer faults with an XPU violation.
>
>BAM-DMUX is a singleton (exactly one instance per SoC), so the
>destination VMID does not need to be a DT property; it is looked up
>from the compatible string's match data instead. Add struct
>bam_dmux_data with a single vmid field, and a shikra_data instance
>hardcoding QCOM_SCM_VMID_NAV for qcom,shikra-bam-dmux.
>
>When match data is present, allocate all BAM_DMUX_NUM_SKB RX buffers as
>a single contiguous dma_alloc_coherent() block and SCM-assign that
>block to HLOS plus the VMID once at probe. This reduces RG consumption
>from 32 to 1. The block is never reclaimed across a modem power cycle
>(bam_dmux_power_off() does not touch it), so the probe-time assignment
>covers every subsequent restart without re-assigning or reclaiming. It
>is reclaimed to HLOS only once, at remove or on a probe error, and if
>that reclaim fails it is leaked rather than returned to the page
>allocator.
>
>Each rx_skbs[] slot is pre-assigned its virtual and DMA address from
>the block, so no per-buffer mapping is needed at power-on. Because the
>coherent block is not page-backed, received payload is copied into a
>regular netdev skb before handoff to the network stack; this is an
>unavoidable extra copy on the XPU-enforced RX path.
>
>Platforms without match data are unaffected: rx_virt stays NULL, no
>coherent memory is allocated, and the per-buffer dma_map_single() path
>is unchanged.
>
>Co-developed-by: Deepak Kumar Singh <deepak.singh@oss.qualcomm.com>
>Signed-off-by: Deepak Kumar Singh <deepak.singh@oss.qualcomm.com>
>Signed-off-by: Vishnu Santhosh <vishnu.santhosh@oss.qualcomm.com>
>---
> drivers/net/wwan/Kconfig | 1 +
> drivers/net/wwan/qcom_bam_dmux.c | 134 ++++++++++++++++++++++++++++++++++++---
> 2 files changed, 125 insertions(+), 10 deletions(-)
>
>diff --git a/drivers/net/wwan/Kconfig b/drivers/net/wwan/Kconfig
>index 958dbc7347fa84ee869439bf8b503037faab8bef..1b133c56231615269698140187ca3141dfe48dbf 100644
>--- a/drivers/net/wwan/Kconfig
>+++ b/drivers/net/wwan/Kconfig
>@@ -65,6 +65,7 @@ config MHI_WWAN_MBIM
> config QCOM_BAM_DMUX
> tristate "Qualcomm BAM-DMUX WWAN network driver"
> depends on (DMA_ENGINE && PM && QCOM_SMEM_STATE) || COMPILE_TEST
>+ select QCOM_SCM
> help
> The BAM Data Multiplexer provides access to the network data channels
> of modems integrated into many older Qualcomm SoCs, e.g. Qualcomm
>diff --git a/drivers/net/wwan/qcom_bam_dmux.c b/drivers/net/wwan/qcom_bam_dmux.c
>index cc6ace8d64371eb8d00c638a39b234ee540b83c9..247230b720e6011876d5c429badbb5a1f34fc576 100644
>--- a/drivers/net/wwan/qcom_bam_dmux.c
>+++ b/drivers/net/wwan/qcom_bam_dmux.c
>@@ -9,10 +9,12 @@
> #include <linux/completion.h>
> #include <linux/dma-mapping.h>
> #include <linux/dmaengine.h>
>+#include <linux/firmware/qcom/qcom_scm.h>
> #include <linux/if_arp.h>
> #include <linux/interrupt.h>
> #include <linux/module.h>
> #include <linux/netdevice.h>
>+#include <linux/of.h>
> #include <linux/platform_device.h>
> #include <linux/pm_runtime.h>
> #include <linux/soc/qcom/smem_state.h>
>@@ -62,6 +64,7 @@ struct bam_dmux_skb_dma {
> struct bam_dmux *dmux;
> struct sk_buff *skb;
> dma_addr_t addr;
>+ void *rx_virt; /* non-NULL: slot in the coherent RX block */
> };
>
> struct bam_dmux {
>@@ -75,6 +78,10 @@ struct bam_dmux {
> struct completion pc_ack_completion;
>
> struct dma_chan *rx, *tx;
>+ /* Single coherent block backing all RX buffers, NULL if unused */
>+ void *rx_buf;
>+ dma_addr_t rx_buf_dma;
>+ u64 rx_buf_perms; /* SCM source-VMID bitmask of rx_buf */
> struct bam_dmux_skb_dma rx_skbs[BAM_DMUX_NUM_SKB];
> struct bam_dmux_skb_dma tx_skbs[BAM_DMUX_NUM_SKB];
> spinlock_t tx_lock; /* Protect tx_skbs, tx_next_skb */
>@@ -92,6 +99,10 @@ struct bam_dmux_netdev {
> u8 ch;
> };
>
>+struct bam_dmux_data {
>+ u32 vmid;
>+};
>+
do we need to introduce dedicated struct just to cover u32?
> static void bam_dmux_pc_vote(struct bam_dmux *dmux, bool enable)
> {
> reinit_completion(&dmux->pc_ack_completion);
>@@ -111,6 +122,9 @@ static bool bam_dmux_skb_dma_map(struct bam_dmux_skb_dma *skb_dma,
> {
> struct device *dev = skb_dma->dmux->dev;
>
>+ if (skb_dma->rx_virt) /* coherent RX slot: addr pre-assigned */
>+ return true;
>+
> skb_dma->addr = dma_map_single(dev, skb_dma->skb->data, skb_dma->skb->len, dir);
> if (dma_mapping_error(dev, skb_dma->addr)) {
> dev_err(dev, "Failed to DMA map buffer\n");
>@@ -124,6 +138,9 @@ static bool bam_dmux_skb_dma_map(struct bam_dmux_skb_dma *skb_dma,
> static void bam_dmux_skb_dma_unmap(struct bam_dmux_skb_dma *skb_dma,
> enum dma_data_direction dir)
> {
>+ if (skb_dma->rx_virt) /* coherent RX slot: nothing to unmap */
>+ return;
>+
> dma_unmap_single(skb_dma->dmux->dev, skb_dma->addr, skb_dma->skb->len, dir);
> skb_dma->addr = 0;
> }
>@@ -468,9 +485,10 @@ static bool bam_dmux_skb_dma_submit_rx(struct bam_dmux_skb_dma *skb_dma)
> {
> struct bam_dmux *dmux = skb_dma->dmux;
> struct dma_async_tx_descriptor *desc;
>+ size_t len = skb_dma->rx_virt ? BAM_DMUX_BUFFER_SIZE : skb_dma->skb->len;
please stick to RCT
please fix it here and for the following where RCT is violated
>
> desc = dmaengine_prep_slave_single(dmux->rx, skb_dma->addr,
>- skb_dma->skb->len, DMA_DEV_TO_MEM,
>+ len, DMA_DEV_TO_MEM,
> DMA_PREP_INTERRUPT);
> if (!desc) {
> dev_err(dmux->dev, "Failed to prepare RX DMA buffer\n");
>@@ -485,6 +503,10 @@ static bool bam_dmux_skb_dma_submit_rx(struct bam_dmux_skb_dma *skb_dma)
>
> static bool bam_dmux_skb_dma_queue_rx(struct bam_dmux_skb_dma *skb_dma, gfp_t gfp)
> {
>+ /* Coherent RX slots have rx_virt and addr pre-assigned at probe. */
>+ if (skb_dma->rx_virt)
>+ return bam_dmux_skb_dma_submit_rx(skb_dma);
>+
> if (!skb_dma->skb) {
> skb_dma->skb = __netdev_alloc_skb(NULL, BAM_DMUX_BUFFER_SIZE, gfp);
> if (!skb_dma->skb)
>@@ -499,9 +521,10 @@ static bool bam_dmux_skb_dma_queue_rx(struct bam_dmux_skb_dma *skb_dma, gfp_t gf
> static void bam_dmux_cmd_data(struct bam_dmux_skb_dma *skb_dma)
> {
> struct bam_dmux *dmux = skb_dma->dmux;
>- struct sk_buff *skb = skb_dma->skb;
>- struct bam_dmux_hdr *hdr = (struct bam_dmux_hdr *)skb->data;
>+ struct bam_dmux_hdr *hdr = skb_dma->rx_virt ? skb_dma->rx_virt :
>+ (struct bam_dmux_hdr *)skb_dma->skb->data;
> struct net_device *netdev = dmux->netdevs[hdr->ch];
>+ struct sk_buff *skb;
>
> if (!netdev || !netif_running(netdev)) {
> dev_warn(dmux->dev, "Data for inactive channel %u\n", hdr->ch);
>@@ -514,10 +537,18 @@ static void bam_dmux_cmd_data(struct bam_dmux_skb_dma *skb_dma)
> return;
> }
>
>- skb_dma->skb = NULL; /* Hand over to network stack */
>-
>- skb_pull(skb, sizeof(*hdr));
>- skb_trim(skb, hdr->len);
>+ if (skb_dma->rx_virt) {
>+ /* Coherent block is not page-backed: copy out to a real skb */
>+ skb = netdev_alloc_skb(netdev, hdr->len);
>+ if (!skb)
>+ return;
>+ skb_put_data(skb, (u8 *)skb_dma->rx_virt + sizeof(*hdr), hdr->len);
>+ } else {
>+ skb = skb_dma->skb;
>+ skb_dma->skb = NULL; /* Hand over to network stack */
>+ skb_pull(skb, sizeof(*hdr));
>+ skb_trim(skb, hdr->len);
>+ }
> skb->dev = netdev;
>
> /* Only Raw-IP/QMAP is supported by this driver */
>@@ -574,10 +605,14 @@ static void bam_dmux_rx_callback(void *data)
> {
> struct bam_dmux_skb_dma *skb_dma = data;
> struct bam_dmux *dmux = skb_dma->dmux;
>- struct sk_buff *skb = skb_dma->skb;
>- struct bam_dmux_hdr *hdr = (struct bam_dmux_hdr *)skb->data;
>+ struct bam_dmux_hdr *hdr;
>
>- bam_dmux_skb_dma_unmap(skb_dma, DMA_FROM_DEVICE);
>+ if (skb_dma->rx_virt) {
>+ hdr = skb_dma->rx_virt; /* coherent RX: no skb to unmap */
>+ } else {
>+ bam_dmux_skb_dma_unmap(skb_dma, DMA_FROM_DEVICE);
>+ hdr = (struct bam_dmux_hdr *)skb_dma->skb->data;
>+ }
>
> if (hdr->magic != BAM_DMUX_HDR_MAGIC) {
> dev_err(dmux->dev, "Invalid magic in header: %#x\n", hdr->magic);
>@@ -644,6 +679,9 @@ static void bam_dmux_free_skbs(struct bam_dmux_skb_dma skbs[],
> for (i = 0; i < BAM_DMUX_NUM_SKB; i++) {
> struct bam_dmux_skb_dma *skb_dma = &skbs[i];
>
>+ if (skb_dma->rx_virt) /* coherent block freed at remove */
>+ continue;
>+
> if (skb_dma->addr)
> bam_dmux_skb_dma_unmap(skb_dma, dir);
> if (skb_dma->skb) {
>@@ -762,6 +800,71 @@ static int __maybe_unused bam_dmux_runtime_resume(struct device *dev)
> return 0;
> }
>
>+static int bam_dmux_alloc_coherent_rx(struct bam_dmux *dmux)
>+{
>+ struct device *dev = dmux->dev;
>+ const struct bam_dmux_data *data = of_device_get_match_data(dev);
>+ size_t size = BAM_DMUX_NUM_SKB * BAM_DMUX_BUFFER_SIZE;
>+ u64 src = BIT_ULL(QCOM_SCM_VMID_HLOS);
>+ struct qcom_scm_vmperm dst[2];
>+ int i, ret;
>+
>+ if (!data)
>+ return 0;
is there actually any chance to really trigger that check?
or just theoretical case?
>+
>+ if (!qcom_scm_is_available())
>+ return -EPROBE_DEFER;
>+
>+ dst[0].vmid = QCOM_SCM_VMID_HLOS;
>+ dst[0].perm = QCOM_SCM_PERM_RW;
>+ dst[1].vmid = data->vmid;
>+ dst[1].perm = QCOM_SCM_PERM_RW;
>+
>+ dmux->rx_buf = dma_alloc_coherent(dev, size, &dmux->rx_buf_dma, GFP_KERNEL);
>+ if (!dmux->rx_buf)
>+ return -ENOMEM;
>+
>+ for (i = 0; i < BAM_DMUX_NUM_SKB; i++) {
>+ dmux->rx_skbs[i].rx_virt = dmux->rx_buf + i * BAM_DMUX_BUFFER_SIZE;
>+ dmux->rx_skbs[i].addr = dmux->rx_buf_dma + i * BAM_DMUX_BUFFER_SIZE;
>+ }
>+
>+ ret = qcom_scm_assign_mem(dmux->rx_buf_dma, size, &src, dst, ARRAY_SIZE(dst));
>+ if (ret) {
>+ dev_err(dev, "SCM assign RX block failed: %d\n", ret);
>+ dma_free_coherent(dev, size, dmux->rx_buf, dmux->rx_buf_dma);
>+ dmux->rx_buf = NULL;
>+ return ret;
>+ }
>+ dmux->rx_buf_perms = src;
>+
>+ return 0;
>+}
>+
>+static void bam_dmux_free_coherent_rx(struct bam_dmux *dmux)
>+{
>+ struct qcom_scm_vmperm hlos = {
>+ .vmid = QCOM_SCM_VMID_HLOS,
>+ .perm = QCOM_SCM_PERM_RW,
>+ };
>+ size_t size = BAM_DMUX_NUM_SKB * BAM_DMUX_BUFFER_SIZE;
>+
>+ if (!dmux->rx_buf)
>+ return;
>+
>+ if (dmux->rx_buf_perms) {
>+ if (qcom_scm_assign_mem(dmux->rx_buf_dma, size, &dmux->rx_buf_perms,
>+ &hlos, 1)) {
>+ dev_err(dmux->dev, "SCM reclaim RX block failed; leaking\n");
>+ return;
>+ }
>+ dmux->rx_buf_perms = 0;
>+ }
>+
>+ dma_free_coherent(dmux->dev, size, dmux->rx_buf, dmux->rx_buf_dma);
>+ dmux->rx_buf = NULL;
>+}
>+
> static int bam_dmux_probe(struct platform_device *pdev)
> {
> struct device *dev = &pdev->dev;
>@@ -809,6 +912,10 @@ static int bam_dmux_probe(struct platform_device *pdev)
> dmux->tx_skbs[i].dmux = dmux;
> }
>
>+ ret = bam_dmux_alloc_coherent_rx(dmux);
>+ if (ret)
>+ return ret;
>+
> /* Runtime PM manages our own power vote.
> * Note that the RX path may be active even if we are runtime suspended,
> * since it is controlled by the remote side.
>@@ -845,6 +952,7 @@ static int bam_dmux_probe(struct platform_device *pdev)
> err_disable_pm:
> pm_runtime_disable(dev);
> pm_runtime_dont_use_autosuspend(dev);
>+ bam_dmux_free_coherent_rx(dmux);
> return ret;
> }
>
>@@ -879,13 +987,19 @@ static void bam_dmux_remove(struct platform_device *pdev)
> disable_irq(dmux->pc_irq);
> bam_dmux_power_off(dmux);
> bam_dmux_free_skbs(dmux->tx_skbs, DMA_TO_DEVICE);
>+ bam_dmux_free_coherent_rx(dmux);
> }
>
> static const struct dev_pm_ops bam_dmux_pm_ops = {
> SET_RUNTIME_PM_OPS(bam_dmux_runtime_suspend, bam_dmux_runtime_resume, NULL)
> };
>
>+static const struct bam_dmux_data shikra_data = {
>+ .vmid = QCOM_SCM_VMID_NAV,
>+};
>+
> static const struct of_device_id bam_dmux_of_match[] = {
>+ { .compatible = "qcom,shikra-bam-dmux", .data = &shikra_data },
> { .compatible = "qcom,bam-dmux" },
> { /* sentinel */ }
> };
>
>--
>2.34.1
^ permalink raw reply
* RE: [PATCH 0/2] net: wwan: qcom_bam_dmux: Alloc RX buffers as a single coherent block
From: Jagielski, Jedrzej @ 2026-07-14 7:53 UTC (permalink / raw)
To: Vishnu Santhosh, Stephan Gerhold, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Loic Poulain, Sergey Ryazanov,
Johannes Berg
Cc: linux-arm-msm@vger.kernel.org, netdev@vger.kernel.org,
devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
chris.lew@oss.qualcomm.com, Deepak Kumar Singh
In-Reply-To: <20260714-qcom-bam-dmux-vmid-ext-v1-0-3f29da7cca76@oss.qualcomm.com>
From: Vishnu Santhosh <vishnu.santhosh@oss.qualcomm.com>
Sent: Tuesday, July 14, 2026 7:33 AM
>On platforms where the modem DMAs into the BAM-DMUX RX data buffers and
>the XPU (eXternal Protection Unit) enforces per-region access control,
>each individually DMA-mapped RX buffer consumes an XPU resource group.
>With only ~16 groups available on Shikra (mDSP, VMID 43 / NAV), the
>per-buffer mappings exhaust the table and inbound transfers fault.
>
>This series adds a qcom,shikra-bam-dmux compatible and have the driver
>select QCOM_SCM_VMID_NAV internally via that compatible's match data.
>When matched, the driver allocates all RX buffers as a single
>contiguous coherent block and SCM-assigns it to HLOS plus the VMID
>once at probe, consuming one XPU resource group instead of many.
>
>Platforms that do not use the qcom,shikra-bam-dmux compatible are
>unaffected: the existing per-buffer dma_map_single() path is
>unchanged.
>
>Signed-off-by: Vishnu Santhosh <vishnu.santhosh@oss.qualcomm.com>
>---
>Vishnu Santhosh (2):
> dt-bindings: net: qcom,bam-dmux: Add qcom,shikra-bam-dmux compatible
> net: wwan: qcom_bam_dmux: Alloc RX buffers as a single coherent block
>
> .../devicetree/bindings/net/qcom,bam-dmux.yaml | 8 +-
> drivers/net/wwan/Kconfig | 1 +
> drivers/net/wwan/qcom_bam_dmux.c | 134 +++++++++++++++++++--
> 3 files changed, 132 insertions(+), 11 deletions(-)
>---
>base-commit: 3b029c035b34bbc693405ddf759f0e9b920c27f1
>change-id: 20260714-qcom-bam-dmux-vmid-ext-d9289db310c1
>
>Best regards,
>--
>Vishnu Santhosh <vishnu.santhosh@oss.qualcomm.com>
Hi Vishnu
you need to specify target tree, net-next for this series i believe
please refer to[1]
[1]
https://docs.kernel.org/process/maintainer-netdev.html#indicating-target-tree
^ permalink raw reply
* Re: [PATCH net v3 0/3] net: stmmac: L3/L4 filter bug fixes
From: Jakub Raczynski @ 2026-07-14 7:47 UTC (permalink / raw)
To: muhammad.nazim.amirul.nazle.asmade
Cc: netdev, andrew+netdev, davem, edumazet, kuba, pabeni, rmk+kernel,
maxime.chevallier, Jose.Abreu, linux-kernel
In-Reply-To: <20260714023716.29865-1-muhammad.nazim.amirul.nazle.asmade@altera.com>
[-- Attachment #1: Type: text/plain, Size: 2955 bytes --]
On Mon, Jul 13, 2026 at 07:37:12PM -0700, muhammad.nazim.amirul.nazle.asmade@altera.com wrote:
> From: Nazim Amirul <muhammad.nazim.amirul.nazle.asmade@altera.com>
>
> This series fixes three bugs in the stmmac L3/L4 TC flower filter
> implementation for the XGMAC2 core. All three patches target net.
>
> The L3/L4 filter match count statistics patch (originally patch 4/4)
> has been split out and will be sent separately against net-next per
> Andrew Lunn's review of v1.
>
> Patch 1 fixes a register corruption bug in the L4 filter port configuration.
> The XGMAC_L4_ADDR register holds both source and destination port match
> values in a single register. The original code overwrites the entire register
> when setting either field, silently erasing the other. This is fixed by
> using a read-modify-write sequence.
>
> Patch 2 fixes the basic flow match parser to properly reject unsupported
> offload requests with -EOPNOTSUPP instead of silently accepting them.
> Unsupported cases include partial protocol masks, non-IPv4 network proto,
> and non-TCP/UDP transport proto. Extack messages are now included so users
> know exactly which part of the match is unsupported. The -EOPNOTSUPP is
> also now returned directly instead of using break, which was silently
> discarding the error on FLOW_CLS_REPLACE operations.
>
> Patch 3 fixes a stale action bug on filter deletion. When a filter entry
> with a drop action is deleted, the action field was not reset, causing
> it to persist and potentially affect subsequent filter configurations.
>
> All three patches fix the original L3/L4 filter implementation introduced in
> 425eabddaf0f ("net: stmmac: Implement L3/L4 Filters using TC Flower").
>
> Changes in v3:
> - Patch 2: add extack messages to each -EOPNOTSUPP return (Jakub Kicinski)
> - Patch 2: return -EOPNOTSUPP directly instead of break to avoid silently
> reporting success on unsupported FLOW_CLS_REPLACE (Sashiko review)
>
> Changes in v2:
> - Split patch 4/4 (ethtool stats) out to net-next per Andrew Lunn's review
>
> Nazim Amirul (3):
> net: stmmac: xgmac: fix l4 filter port overwrite on register update
> net: stmmac: fix l3l4 filter rejecting unsupported offload requests
> net: stmmac: reset residual action in L3L4 filters on delete
>
> .../ethernet/stmicro/stmmac/dwxgmac2_core.c | 28 ++++++++-------
> .../net/ethernet/stmicro/stmmac/stmmac_tc.c | 35 +++++++++++++++++++
> 2 files changed, 51 insertions(+), 12 deletions(-)
>
I think you probably should have done [PATCH RESEND] and it looks bit
funky on mailing list (double cover letter ?), but is fine on patchwork.
One nit:
- Not sure EOPNOTSUPP is optimal, as this is just wrong config and maybe
something like EINVAL would be better? But thats whatever
I have no means to test this now, but 1st patch seems to solve lot of
encountered issues.
For the series:
Reviewed-by: Jakub Raczynski <j.raczynski@samsung.com>
BR
Jakub Raczynski
[-- Attachment #2: Type: text/plain, Size: 0 bytes --]
^ permalink raw reply
* Re: [PATCH rdma-next 08/13] RDMA/cgroup: Scope rdma cgroup device visibility to the net namespace
From: Jiri Pirko @ 2026-07-14 7:39 UTC (permalink / raw)
To: Tao Cui
Cc: Michal Koutný, linux-rdma, cgroups, netdev, linux-s390,
linux-kselftest, jgg, leon, parav, mbloch, cmeiohas,
roman.gushchin, bvanassche, zyjzyj2000, shuah, tj, hannes,
alibuda, dust.li, sidraya, wenjia
In-Reply-To: <2fc297ce-7259-4410-9d86-ccc32485622f@linux.dev>
Tue, Jul 14, 2026 at 04:28:01AM +0200, cui.tao@linux.dev wrote:
>
>
>在 2026/7/13 17:34, Jiri Pirko 写道:
>> Thu, Jul 09, 2026 at 03:04:23PM +0200, mkoutny@suse.com wrote:
>>> Hi.
>>>
>>> On Thu, Jul 09, 2026 at 11:55:27AM +0200, Jiri Pirko <jiri@resnulli.us> wrote:
>>>> index 993446ab66d0..4523c1884d67 100644
>>>> --- a/Documentation/admin-guide/cgroup-v2.rst
>>>> +++ b/Documentation/admin-guide/cgroup-v2.rst
>>>> @@ -2752,6 +2752,13 @@ RDMA
>>>> The "rdma" controller regulates the distribution and accounting of
>>>> RDMA resources.
>>>>
>>>> +When RDMA devices are isolated per network namespace (exclusive mode),
>>>> +device names are unique only within a network namespace. The device lines
>>>> +below are therefore scoped to the reading or writing process's network
>>>> +namespace: only devices accessible from that namespace are listed, and a
>>>> +limit is applied to the device of that name in that namespace. Configure
>>>> +limits from the same network namespace as the workloads.
>>>
>>> OK.
>>>
>>>> --- a/include/linux/cgroup_rdma.h
>>>> +++ b/include/linux/cgroup_rdma.h
>>>> @@ -7,6 +7,7 @@
>>>> #define _CGROUP_RDMA_H
>>>>
>>>> #include <linux/cgroup.h>
>>>> +#include <net/net_namespace.h>
>>>>
>>>> enum rdmacg_resource_type {
>>>> RDMACG_RESOURCE_HCA_HANDLE,
>>>> @@ -34,6 +35,15 @@ struct rdmacg_device {
>>>> struct list_head dev_node;
>>>> struct list_head rpools;
>>>> char *name;
>>>> + /*
>>>> + * Net namespace the device belongs to. @netns_shared mirrors
>>>> + * ib_devices_shared_netns: when true the device is visible from every
>>>> + * net namespace (shared mode); otherwise @net is the only namespace
>>>> + * that may see and configure it. @netns_shared is updated when the
>>>> + * sharing mode changes, so use {READ,WRITE}_ONCE() to access it.
>>>> + */
>>>> + possible_net_t net;
>>>> + bool netns_shared;
>>>
>>> Any reason to store the netns_shared split per device? (IIUC, it's a
>>> global parameter.)
>>
>> No reason, changed.
>>
>Hi Jiri,
>
>A question on the v2 you mentioned to Michal.
>
>Once netns_shared stops being cached per rdmacg_device,
>rdmacg_device_visible() in kernel/cgroup/rdma.c still needs the current
>sharing mode, whose authoritative value lives in the IB core
>(ib_devices_shared_netns). How do you plan to expose it there without
>the generic cgroup controller reaching back into drivers/infiniband/?
>Exporting the global, or keeping an IB-side update hook, both feel a bit
>awkward; it would be good to see which direction you took.
Exposing this from ib side is not doable, as IB may be compiled as a
module. So what I do is I intrododuce "static bool rdmacg_netns_shared"
in cgroup/rdma.c and exported function rdmacg_set_netns_shared() to set
it from IB.
>
>On the mechanism itself: it's the right call that rdmacg_try_charge()
>stays out of the scoping. Charging takes the rdmacg_device pointer
>directly (no name lookup), and a task can only charge a device it
>already holds a handle to, so applying visibility there would be wrong.
>The scoping deliberately touches only the name-based lookup (the write
>path) and the enumeration (read/show) paths -- worth keeping that
>invariant in mind so a later patch doesn't grow the filter.
>
>Thanks,
>Tao> Thanks!
>>
>>>
>>> Thanks,
>>> Michal
>>
>>
>>
>
^ permalink raw reply
* Re: [PATCH 2/2] net: wwan: qcom_bam_dmux: Alloc RX buffers as a single coherent block
From: Stephan Gerhold @ 2026-07-14 7:35 UTC (permalink / raw)
To: Vishnu Santhosh
Cc: Stephan Gerhold, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Loic Poulain, Sergey Ryazanov, Johannes Berg,
linux-arm-msm, netdev, devicetree, linux-kernel, chris.lew,
Deepak Kumar Singh
In-Reply-To: <20260714-qcom-bam-dmux-vmid-ext-v1-2-3f29da7cca76@oss.qualcomm.com>
On Tue, Jul 14, 2026 at 11:02:32AM +0530, Vishnu Santhosh wrote:
> On Qualcomm SoCs where the modem (e.g. the mDSP on Shikra, VMID 43 /
> NAV) is the AXI master for BAM-DMUX RX transfers and the XPU enforces
> per-region access control, each individually DMA-mapped RX buffer
> requires its own XPU resource group (RG). With ~16 RGs available, the
> 32 per-buffer dma_map_single() calls exhaust the table and the first
> inbound transfer faults with an XPU violation.
>
> BAM-DMUX is a singleton (exactly one instance per SoC), so the
> destination VMID does not need to be a DT property; it is looked up
> from the compatible string's match data instead. Add struct
> bam_dmux_data with a single vmid field, and a shikra_data instance
> hardcoding QCOM_SCM_VMID_NAV for qcom,shikra-bam-dmux.
>
> When match data is present, allocate all BAM_DMUX_NUM_SKB RX buffers as
> a single contiguous dma_alloc_coherent() block and SCM-assign that
> block to HLOS plus the VMID once at probe. This reduces RG consumption
> from 32 to 1. The block is never reclaimed across a modem power cycle
> (bam_dmux_power_off() does not touch it), so the probe-time assignment
> covers every subsequent restart without re-assigning or reclaiming. It
> is reclaimed to HLOS only once, at remove or on a probe error, and if
> that reclaim fails it is leaked rather than returned to the page
> allocator.
>
> Each rx_skbs[] slot is pre-assigned its virtual and DMA address from
> the block, so no per-buffer mapping is needed at power-on. Because the
> coherent block is not page-backed, received payload is copied into a
> regular netdev skb before handoff to the network stack; this is an
> unavoidable extra copy on the XPU-enforced RX path.
>
> Platforms without match data are unaffected: rx_virt stays NULL, no
> coherent memory is allocated, and the per-buffer dma_map_single() path
> is unchanged.
>
> Co-developed-by: Deepak Kumar Singh <deepak.singh@oss.qualcomm.com>
> Signed-off-by: Deepak Kumar Singh <deepak.singh@oss.qualcomm.com>
> Signed-off-by: Vishnu Santhosh <vishnu.santhosh@oss.qualcomm.com>
So how do you handle TX buffers? Right now, they are just passed on from
the net subsystem. There can be up to 32 TX buffers in progress as well.
Overall, I have mixed feelings about this patch. It looks reasonably
simple, but fundamentally I don't understand why we need to go back to
the old days of implementing protection using a highly limited MPU (in
your case: the xPU).
Why does the setup of BAM-DMUX differ e.g. from the setup for the crypto
engine? Crypto is also using bam-dma, but it avoids this inflexibility
by making use of the &apps_smmu. Is BAM-DMUX not covered by the SMMU? Or
did you just decide to bypass the SMMU in this case? (If so: Why?)
If you had BAM-DMUX mapped using the SMMU you would get all of this for
free. No changes would be needed in the BAM-DMUX driver ...
Thanks,
Stephan
^ permalink raw reply
* [PATCH] amt: fix use-after-free in AMT delayed works
From: Shihuang Liu @ 2026-07-14 7:27 UTC (permalink / raw)
To: netdev
Cc: ap420073, andrew+netdev, davem, edumazet, kuba, pabeni,
linux-kernel, Shihuang Liu, stable
When an AMT device is removed, pending delayed works can still access
the freed amt_dev structure, which may result in kernel crashes or
memory corruption.
amt_dev_stop() cancels req_wq and discovery_wq with
cancel_delayed_work_sync(), but these works can be scheduled again
from event_wq after the cancellation. This allows delayed works to
access the freed amt_dev structure after the netdev has been released.
The following is a simple race scenario:
CPU0 CPU1
amt_dev_stop()
cancel_delayed_work_sync()
amt_event_work()
mod_delayed_work(req_wq)
free netdev
req_wq accesses freed amt_dev
Use disable_delayed_work_sync() in amt_dev_stop() to prevent req_wq and
discovery_wq from being queued again and wait for running work items
to complete.
The delayed works are disabled after initialization in
amt_newlink() and enabled only when the device is successfully opened.
This keeps the delayed work lifecycle synchronized with the lifetime
of the AMT device.
Fixes: cbc21dc1cfe9 ("amt: add data plane of amt interface")
Cc: stable@vger.kernel.org
Signed-off-by: Shihuang Liu <shlomojune6@gmail.com>
---
drivers/net/amt.c | 14 +++++++++++---
1 file changed, 11 insertions(+), 3 deletions(-)
diff --git a/drivers/net/amt.c b/drivers/net/amt.c
index 951dd10e192b..7eb871b9b7e1 100644
--- a/drivers/net/amt.c
+++ b/drivers/net/amt.c
@@ -2995,9 +2995,15 @@ static int amt_dev_open(struct net_device *dev)
amt->event_idx = 0;
amt->nr_events = 0;
+ enable_delayed_work(&amt->discovery_wq);
+ enable_delayed_work(&amt->req_wq);
+
err = amt_socket_create(amt);
- if (err)
+ if (err) {
+ disable_delayed_work(&amt->req_wq);
+ disable_delayed_work(&amt->discovery_wq);
return err;
+ }
amt->req_cnt = 0;
amt->remote_ip = 0;
@@ -3023,8 +3029,8 @@ static int amt_dev_stop(struct net_device *dev)
struct sock *sk;
int i;
- cancel_delayed_work_sync(&amt->req_wq);
- cancel_delayed_work_sync(&amt->discovery_wq);
+ disable_delayed_work_sync(&amt->req_wq);
+ disable_delayed_work_sync(&amt->discovery_wq);
cancel_delayed_work_sync(&amt->secret_wq);
/* shutdown */
@@ -3278,6 +3284,8 @@ static int amt_newlink(struct net_device *dev,
INIT_DELAYED_WORK(&amt->req_wq, amt_req_work);
INIT_DELAYED_WORK(&amt->secret_wq, amt_secret_work);
INIT_WORK(&amt->event_wq, amt_event_work);
+ disable_delayed_work(&amt->req_wq);
+ disable_delayed_work(&amt->discovery_wq);
INIT_LIST_HEAD(&amt->tunnel_list);
return 0;
err:
--
2.43.0
^ permalink raw reply related
* Re: [PATCH net] ethtool: Embed FEC hist ranges as buffer in struct
From: Vadim Fedorenko @ 2026-07-14 7:24 UTC (permalink / raw)
To: Eric Joyner, netdev
Cc: Michael Chan, Pavan Chebbi, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Saeed Mahameed,
Leon Romanovsky, Tariq Toukan, Mark Bloch, Simon Horman,
Maxime Chevallier, Brett Creeley, Breno Leitao, Nikhil P. Rao
In-Reply-To: <303a0166-efd5-48b1-8a52-39780095b7a1@amd.com>
On 13.07.2026 23:37, Eric Joyner wrote:
> On 7/11/2026 2:03 PM, Vadim Fedorenko wrote:
>> Caution: This message originated from an External Source. Use proper caution
>> when opening attachments, clicking links, or responding.
>>
>>
>> On 11/07/2026 00:00, Eric Joyner wrote:
>>> When a driver's .get_fec_stats() handler is called and the driver
>>> supports FEC histogram stats, the driver supplies the histogram bin
>>> ranges via a pointer. This pointer is assigned while under the netdev
>>> ops lock in fec_prepare_data(), but the actual data is only read after
>>> the lock is released; so this allows the driver to change the ranges
>>> (e.g. from another .get_fec_stats() call) while the current call chain
>>> is reading them in fec_fill_reply().
>>>
>>> Fix this by embedding a buffer for the driver-supplied ranges in struct
>>> ethtool_fec_hist instead of using a pointer; this ensures there's an
>>> ethtool core-owned consistent copy that can be used after the netdev ops
>>> lock is dropped and later in fec_fill_reply(). While some drivers like
>>> bnxt use a constant struct for their ranges and won't be affected by
>>> this issue, others like mlx5 (and eventually ionic) will use a
>>> dynamically constructed range struct and could potentially run into an
>>> issue.
>>
>> I didn't like the idea of dynamic range, FEC is not changing while the
>> link is UP, I don't see a reason to dynamically reconstruct histogram
>> bins every single call. And the histogram itself is stable per HW per
>> FEC, can be constant pre-defined struct in a driver, like in bnxt.
>>
>> But if dynamic allocation is the only option, then yes, we have to
>> change this ABI.
>
> We can discuss this more.
>
> I think overall drivers aren't going to need to dynamically allocate a range; I
> mention ionic but at the moment I think there's only going to be two possible
> FEC ranges; the sixteen bin one for RS(544,514) and I think what should be a
> reduced size eight bin one for low latency RS-FEC RS(272,258) (unlike the 802.3
> spec the Ethernet Consortium Spec for LL RS-FEC doesn't talk about a histogram,
> but FEC math says those parameters can only correct up to 7-bit errors).
>
> So one option could be to have the pointer be required to point to static
> memory; or possibly a pre-defined histogram range entry in the kernel? I don't
> see any other drivers currently combining multiple bit-error counts into one bin
> and I wasn't sure if that's something the mlx5 driver actually uses, too.
The problem of mlx5 driver is that for RS(528,514) CX6 firmware returns only 4
bins with merged ranges, while newer generations have 7 bins for the same FEC.
That means core kernel won't have all possible options and the drivers have to
have their own static lists of ranges. But it's still stable list per HW per FEC
as I stated before.
> OTOH, doing this dynamic range calculation should be computationally pretty
> cheap overall, and provides flexibility without keeping or adding new
> concurrency problems (which is an important concern!), so I don't mind the
> current approach even if it does look wasteful.
>
> - Eric
>
>>
>>> Since the kernel API changed here, change the in-tree drivers that
>>> report FEC histogram stats to copy their ranges instead of just
>>> supplying a pointer.
>>>
>>> Fixes: cc2f08129925 ("ethtool: add FEC bins histogram report")
>>> Signed-off-by: Eric Joyner <eric.joyner@amd.com>
>
^ permalink raw reply
* Re: [PATCH 1/2] dt-bindings: net: qcom,bam-dmux: Add qcom,shikra-bam-dmux compatible
From: Stephan Gerhold @ 2026-07-14 7:11 UTC (permalink / raw)
To: Vishnu Santhosh
Cc: Stephan Gerhold, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Loic Poulain, Sergey Ryazanov, Johannes Berg,
linux-arm-msm, netdev, devicetree, linux-kernel, chris.lew,
Deepak Kumar Singh
In-Reply-To: <20260714-qcom-bam-dmux-vmid-ext-v1-1-3f29da7cca76@oss.qualcomm.com>
On Tue, Jul 14, 2026 at 11:02:31AM +0530, Vishnu Santhosh wrote:
> On platforms where the modem DMAs into the BAM-DMUX RX data buffers and
> the XPU enforces per-region access control, each individually
> DMA-mapped RX buffer consumes an XPU resource group. With only ~16
> groups available, the per-buffer mappings exhaust the table and inbound
> transfers fault.
>
> Add qcom,shikra-bam-dmux as an additional compatible for the Shikra SoC,
> paired with the generic qcom,bam-dmux fallback, so the driver can match
> on it via its of_device_id table.
>
> Co-developed-by: Deepak Kumar Singh <deepak.singh@oss.qualcomm.com>
> Signed-off-by: Deepak Kumar Singh <deepak.singh@oss.qualcomm.com>
> Signed-off-by: Vishnu Santhosh <vishnu.santhosh@oss.qualcomm.com>
IMHO (the DT maintainers might disagree) this is not a different
BAM DMUX hardware block on Shikra, just a different firmware
configuration. I'm sure the existing SoCs also have those XPU blocks,
they just don't make use of it.
Personally, I would rather describe this with an optional "qcom,vmid"
property similar to your bam-dma patch, allowed for all SoCs.
Thanks,
Stephan
^ permalink raw reply
* [PATCH net] tipc: prevent node timer rearm after peer removal
From: David Lee @ 2026-07-14 7:06 UTC (permalink / raw)
To: Jon Maloy
Cc: David Lee, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Richard Alpe, Ying Xue,
Dominik 'Disconnect3d' Czarnota, netdev, tipc-discussion,
linux-kernel
TIPC node deletion removes the node from lookup tables, calls
timer_delete_sync(), and drops the timer reference. This stops an already
running timer callback, but it does not prevent another racing path from
rearming the same timer after deletion has started.
Synthetic UDP discovery can race TIPC_NL_PEER_REMOVE and link property
updates. When discovery recreates the first link, tipc_node_check_dest()
calls mod_timer() and retakes the timer reference. If that happens after
tipc_node_delete() has deleted the timer, the orphaned timer can later run
after network namespace teardown has freed the per-net TIPC state and
broadcast link.
Use timer_shutdown_sync() when deleting a node. Shutdown has the same
synchronization effect as timer_delete_sync(), and also prevents future
rearming through mod_timer().
Fixes: b34040227be7 ("tipc: add peer removal functionality")
Signed-off-by: David Lee <david.lee@trailofbits.com>
Assisted-by: Codex:gpt-5.5
---
Trail of Bits has a reproducer for this bug demonstrating Kernel Panic which can be shared further if needed.
net/tipc/node.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/net/tipc/node.c b/net/tipc/node.c
index 262b39ecf5f8..5b3ee44347be 100644
--- a/net/tipc/node.c
+++ b/net/tipc/node.c
@@ -638,7 +638,7 @@ static void tipc_node_delete(struct tipc_node *node)
trace_tipc_node_delete(node, true, " ");
tipc_node_delete_from_list(node);
- timer_delete_sync(&node->timer);
+ timer_shutdown_sync(&node->timer);
tipc_node_put(node);
}
--
2.43.0
^ permalink raw reply related
* Re: [PATCH net v3] tun/tap & vhost-net: make qdisc backpressure opt-in via IFF_BACKPRESSURE
From: Simon Schippers @ 2026-07-14 6:57 UTC (permalink / raw)
To: Michael S . Tsirkin
Cc: Simon Horman, Jonathan Corbet, Shuah Khan, Andrew Lunn,
Tim Gebauer, Brett Sheffield, linux-doc, linux-kernel,
Willem de Bruijn, Jason Wang, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, netdev
In-Reply-To: <20260709095511.168235-1-simon.schippers@tu-dortmund.de>
On 7/9/26 11:55, Simon Schippers wrote:
> Commit 1d6e569b7d0c ("tun/tap & vhost-net: avoid ptr_ring tail-drop
> when a qdisc is present") did not show a relevant performance regression
> in my testing but on Brett Sheffield's librecast testbed it shows a
> significant performance drop in a IPv6 multicast testcase. The regression
> can be pinpointed when multiple iperf3 TCP threads are sending. For 8
> threads the performance dropped from 13.5 Gbit/s to 9.13 Gbit/s. This is
> the reason why this patch makes the qdisc backpressure behavior opt-in.
>
> One option to accomplish the opt-in would be to set the default qdisc to
> noqueue at init. However this may also break userspace as users might
> have chosen a custom qdisc even though most of the qdiscs did nothing
> for tun/tap in the past due to missing backpressure...
>
> This is the reason why in this patch, the flag IFF_BACKPRESSURE is
> introduced instead which is required to enable the backpressure logic.
> This means the stopping logic in tun_net_xmit() and the waking logic in
> __tun_wake_queue() are skipped if the flag is disabled. Setting
> IFF_BACKPRESSURE makes an attached qdisc effective by stopping the queue
> instead of tail-dropping when the internal ring is full.
>
> To avoid a possible stall due to disabling IFF_BACKPRESSURE, the new
> helper tun_force_wake_queue() is implemented. The helper safely wakes the
> respective netdev queue and resets cons_cnt while the consumer_lock and
> the producer_lock of the ring are held. The helper is run in tun_attach()
> when a queue (re)attaches, in tun_set_iff() for attached tfiles, and
> in tun_queue_resize().
>
> The documentation in tuntap.rst is updated accordingly.
>
> Fixes: 1d6e569b7d0c ("tun/tap & vhost-net: avoid ptr_ring tail-drop when a qdisc is present")
> Reported-by: Brett Sheffield <brett@librecast.net>
> Closes: https://lore.kernel.org/netdev/akVnoOYQOrt8k-Gu@karahi.librecast.net/T/#u
> Signed-off-by: Simon Schippers <simon.schippers@tu-dortmund.de>
> ---
> V2 -> V3:
> - As suggested by MST: Clarify in tuntap.rst and the UAPI header what
> enabling IFF_BACKPRESSURE opts into: an attached qdisc becomes effective
> instead of the driver tail-dropping when the internal ring is full.
> - Avoid lines over 75 characters.
> - Update comment in tun_net_xmit() to include IFF_BACKPRESSURE.
> - Brett: Update in commit message that the referenced tests were TCP.
>
> V1 -> V2:
> - Sashiko: Ensure detached queues are woken on re-attach by calling the
> new tun_force_wake_queue() helper from tun_attach(), and reuse it
> across the existing wake paths.
> - Specify the failing test case in the commit message.
>
> V1: https://lore.kernel.org/netdev/20260704112058.95421-1-simon.schippers@tu-dortmund.de/T/#u
> V2: https://lore.kernel.org/netdev/20260706094242.115992-1-simon.schippers@tu-dortmund.de/T/#u
>
> Documentation/networking/tuntap.rst | 22 +++++++++++++
> drivers/net/tun.c | 51 ++++++++++++++++++++---------
> include/uapi/linux/if_tun.h | 4 +++
> tools/include/uapi/linux/if_tun.h | 1 +
> 4 files changed, 62 insertions(+), 16 deletions(-)
>
> diff --git a/Documentation/networking/tuntap.rst b/Documentation/networking/tuntap.rst
> index 4d7087f727be..5921a924c2ae 100644
> --- a/Documentation/networking/tuntap.rst
> +++ b/Documentation/networking/tuntap.rst
> @@ -206,6 +206,28 @@ enable is true we enable it, otherwise we disable it::
> return ioctl(fd, TUNSETQUEUE, (void *)&ifr);
> }
>
> +3.4 qdisc backpressure
> +----------------------
> +
> +Starting with Linux 7.2, IFF_BACKPRESSURE can be set to enable qdisc
> +backpressure. Without it, TX drops occur when the internal ring buffer
> +is full, so any attached qdisc is effectively bypassed and applications
> +only learn about congestion through those drops.
> +
> +With it, the kernel stops instead, letting the qdisc hold and schedule
> +packets, so its AQM, shaping and fairness actually apply. This helps
> +protocols like TCP, which cut throughput in reaction to packet drops.
> +With IFF_BACKPRESSURE, drops then only occur as a rare race. Backpressure
> +requires a qdisc to be attached and has no effect with noqueue.
> +
> +The txqueuelen can be reduced alongside this flag to further shift
> +buffering into the qdisc and reduce bufferbloat, but comes at possible
> +performance cost.
> +
> +When running multiple network streams in parallel through a single
> +TUN/TAP queue, the flag may reduce performance due to the extra overhead
> +of the backpressure mechanism.
> +
> Universal TUN/TAP device driver Frequently Asked Question
> =========================================================
>
> diff --git a/drivers/net/tun.c b/drivers/net/tun.c
> index ffbe6f13fb1f..5941e8f302ea 100644
> --- a/drivers/net/tun.c
> +++ b/drivers/net/tun.c
> @@ -98,7 +98,8 @@ static void tun_default_link_ksettings(struct net_device *dev,
> #define TUN_FASYNC IFF_ATTACH_QUEUE
>
> #define TUN_FEATURES (IFF_NO_PI | IFF_ONE_QUEUE | IFF_VNET_HDR | \
> - IFF_MULTI_QUEUE | IFF_NAPI | IFF_NAPI_FRAGS)
> + IFF_MULTI_QUEUE | IFF_NAPI | IFF_NAPI_FRAGS | \
> + IFF_BACKPRESSURE)
>
> #define GOODCOPY_LEN 128
>
> @@ -694,6 +695,20 @@ static void tun_detach_all(struct net_device *dev)
> module_put(THIS_MODULE);
> }
>
> +static void tun_force_wake_queue(struct tun_struct *tun,
> + struct tun_file *tfile)
> +{
> + /* Ensure that the producer can not stop the
> + * queue concurrently by taking locks.
> + */
> + spin_lock_bh(&tfile->tx_ring.consumer_lock);
> + spin_lock(&tfile->tx_ring.producer_lock);
> + netif_wake_subqueue(tun->dev, tfile->queue_index);
> + tfile->cons_cnt = 0;
> + spin_unlock(&tfile->tx_ring.producer_lock);
> + spin_unlock_bh(&tfile->tx_ring.consumer_lock);
> +}
> +
> static int tun_attach(struct tun_struct *tun, struct file *file,
> bool skip_filter, bool napi, bool napi_frags,
> bool publish_tun)
> @@ -737,11 +752,9 @@ static int tun_attach(struct tun_struct *tun, struct file *file,
> goto out;
> }
>
> - spin_lock(&tfile->tx_ring.consumer_lock);
> - tfile->cons_cnt = 0;
> - spin_unlock(&tfile->tx_ring.consumer_lock);
> tfile->queue_index = tun->numqueues;
> tfile->socket.sk->sk_shutdown &= ~RCV_SHUTDOWN;
> + tun_force_wake_queue(tun, tfile);
>
> if (tfile->detached) {
> /* Re-attach detached tfile, updating XDP queue_index */
> @@ -1077,7 +1090,8 @@ static netdev_tx_t tun_net_xmit(struct sk_buff *skb, struct net_device *dev)
>
> spin_lock(&tfile->tx_ring.producer_lock);
> ret = __ptr_ring_produce(&tfile->tx_ring, skb);
> - if (!qdisc_txq_has_no_queue(queue) &&
> + if ((tun->flags & IFF_BACKPRESSURE) &&
> + !qdisc_txq_has_no_queue(queue) &&
> __ptr_ring_check_produce(&tfile->tx_ring) == -ENOSPC) {
> netif_tx_stop_queue(queue);
> /* Paired with smp_mb() in __tun_wake_queue() */
> @@ -1088,8 +1102,8 @@ static netdev_tx_t tun_net_xmit(struct sk_buff *skb, struct net_device *dev)
> spin_unlock(&tfile->tx_ring.producer_lock);
>
> if (ret) {
> - /* This should be a rare case if a qdisc is present, but
> - * can happen due to lltx.
> + /* This should be a rare case if IFF_BACKPRESSURE is enabled and
> + * a qdisc is present, but can happen due to lltx.
> * Since skb_tx_timestamp(), skb_orphan(),
> * run_ebpf_filter() and pskb_trim() could have tinkered
> * with the SKB, returning NETDEV_TX_BUSY is unsafe and
> @@ -2151,8 +2165,12 @@ static ssize_t tun_put_user(struct tun_struct *tun,
> static void __tun_wake_queue(struct tun_struct *tun,
> struct tun_file *tfile, int consumed)
> {
> - struct netdev_queue *txq = netdev_get_tx_queue(tun->dev,
> - tfile->queue_index);
> + struct netdev_queue *txq;
> +
> + if (!(tun->flags & IFF_BACKPRESSURE))
> + return;
> +
> + txq = netdev_get_tx_queue(tun->dev, tfile->queue_index);
>
> /* Paired with smp_mb__after_atomic() in tun_net_xmit() */
> smp_mb();
> @@ -2764,7 +2782,7 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr)
> struct tun_struct *tun;
> struct tun_file *tfile = file->private_data;
> struct net_device *dev;
> - int err;
> + int err, i;
>
> if (tfile->detached)
> return -EINVAL;
> @@ -2893,8 +2911,12 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr)
> /* Make sure persistent devices do not get stuck in
> * xoff state.
> */
> - if (netif_running(tun->dev))
> - netif_tx_wake_all_queues(tun->dev);
> + if (netif_running(tun->dev)) {
> + for (i = 0; i < tun->numqueues; i++) {
> + tfile = rtnl_dereference(tun->tfiles[i]);
> + tun_force_wake_queue(tun, tfile);
> + }
> + }
>
> strscpy(ifr->ifr_name, tun->dev->name);
> return 0;
> @@ -3693,10 +3715,7 @@ static int tun_queue_resize(struct tun_struct *tun)
> if (!ret) {
> for (i = 0; i < tun->numqueues; i++) {
> tfile = rtnl_dereference(tun->tfiles[i]);
> - spin_lock(&tfile->tx_ring.consumer_lock);
> - netif_wake_subqueue(tun->dev, tfile->queue_index);
> - tfile->cons_cnt = 0;
> - spin_unlock(&tfile->tx_ring.consumer_lock);
> + tun_force_wake_queue(tun, tfile);
> }
> }
>
> diff --git a/include/uapi/linux/if_tun.h b/include/uapi/linux/if_tun.h
> index 79d53c7a1ebd..a0ddc50a7534 100644
> --- a/include/uapi/linux/if_tun.h
> +++ b/include/uapi/linux/if_tun.h
> @@ -69,6 +69,10 @@
> #define IFF_NAPI_FRAGS 0x0020
> /* Used in TUNSETIFF to bring up tun/tap without carrier */
> #define IFF_NO_CARRIER 0x0040
> +/* Stop the queue instead of dropping when the internal ring is full, so an
> + * attached qdisc applies backpressure instead of being bypassed.
> + */
> +#define IFF_BACKPRESSURE 0x0080
> #define IFF_NO_PI 0x1000
> /* This flag has no real effect */
> #define IFF_ONE_QUEUE 0x2000
> diff --git a/tools/include/uapi/linux/if_tun.h b/tools/include/uapi/linux/if_tun.h
> index 2ec07de1d73b..97b670f5bc0a 100644
> --- a/tools/include/uapi/linux/if_tun.h
> +++ b/tools/include/uapi/linux/if_tun.h
> @@ -67,6 +67,7 @@
> #define IFF_TAP 0x0002
> #define IFF_NAPI 0x0010
> #define IFF_NAPI_FRAGS 0x0020
> +#define IFF_BACKPRESSURE 0x0080
> #define IFF_NO_PI 0x1000
> /* This flag has no real effect */
> #define IFF_ONE_QUEUE 0x2000
Hi Michael, WDYT?
Thanks!
^ permalink raw reply
* Re: [PATCH bpf-next v3 13/15] libbpf: Support attaching struct_ops to a cgroup
From: Emil Tsalapatis @ 2026-07-14 6:48 UTC (permalink / raw)
To: Amery Hung, bpf
Cc: netdev, alexei.starovoitov, andrii, daniel, eddyz87, memxor,
martin.lau, shakeel.butt, roman.gushchin, kuniyu, kerneljasonxing,
kernel-team
In-Reply-To: <20260706171918.317102-14-ameryhung@gmail.com>
On Mon Jul 6, 2026 at 1:19 PM EDT, Amery Hung wrote:
> From: Martin KaFai Lau <martin.lau@kernel.org>
>
> Add bpf_map__attach_cgroup_opts() to attach a struct_ops map to a cgroup
> through a BPF link.
>
> Also extend struct bpf_prog_query_opts with a type_id field so a
> BPF_STRUCT_OPS query on a cgroup can select the struct_ops type to
> enumerate.
>
> Signed-off-by: Martin KaFai Lau <martin.lau@kernel.org>
> Signed-off-by: Amery Hung <ameryhung@gmail.com>
Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>
> ---
> tools/lib/bpf/bpf.c | 2 ++
> tools/lib/bpf/bpf.h | 3 +-
> tools/lib/bpf/libbpf.c | 64 ++++++++++++++++++++++++++++++++++++++++
> tools/lib/bpf/libbpf.h | 3 ++
> tools/lib/bpf/libbpf.map | 1 +
> 5 files changed, 72 insertions(+), 1 deletion(-)
>
> diff --git a/tools/lib/bpf/bpf.c b/tools/lib/bpf/bpf.c
> index 96819c082c77..a9de7f107cf7 100644
> --- a/tools/lib/bpf/bpf.c
> +++ b/tools/lib/bpf/bpf.c
> @@ -934,6 +934,7 @@ int bpf_link_create(int prog_fd, int target_fd,
> case BPF_CGROUP_GETSOCKOPT:
> case BPF_CGROUP_SETSOCKOPT:
> case BPF_LSM_CGROUP:
> + case BPF_STRUCT_OPS:
> relative_fd = OPTS_GET(opts, cgroup.relative_fd, 0);
> relative_id = OPTS_GET(opts, cgroup.relative_id, 0);
> if (relative_fd && relative_id)
> @@ -1056,6 +1057,7 @@ int bpf_prog_query_opts(int target, enum bpf_attach_type type,
> attr.query.attach_type = type;
> attr.query.query_flags = OPTS_GET(opts, query_flags, 0);
> attr.query.count = OPTS_GET(opts, count, 0);
> + attr.query.type_id = OPTS_GET(opts, type_id, 0);
> attr.query.prog_ids = ptr_to_u64(OPTS_GET(opts, prog_ids, NULL));
> attr.query.link_ids = ptr_to_u64(OPTS_GET(opts, link_ids, NULL));
> attr.query.prog_attach_flags = ptr_to_u64(OPTS_GET(opts, prog_attach_flags, NULL));
> diff --git a/tools/lib/bpf/bpf.h b/tools/lib/bpf/bpf.h
> index 7534a593edae..490e8cb4ba53 100644
> --- a/tools/lib/bpf/bpf.h
> +++ b/tools/lib/bpf/bpf.h
> @@ -637,9 +637,10 @@ struct bpf_prog_query_opts {
> __u32 *link_ids;
> __u32 *link_attach_flags;
> __u64 revision;
> + __u32 type_id;
> size_t :0;
> };
> -#define bpf_prog_query_opts__last_field revision
> +#define bpf_prog_query_opts__last_field type_id
>
> /**
> * @brief **bpf_prog_query_opts()** queries the BPF programs and BPF links
> diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
> index 7162146280a8..50a83b9217e4 100644
> --- a/tools/lib/bpf/libbpf.c
> +++ b/tools/lib/bpf/libbpf.c
> @@ -14104,6 +14104,70 @@ struct bpf_link *bpf_map__attach_struct_ops(const struct bpf_map *map)
> return &link->link;
> }
>
> +struct bpf_link *bpf_map__attach_cgroup_opts(const struct bpf_map *map, int cgroup_fd,
> + const struct bpf_cgroup_opts *opts)
> +{
> + LIBBPF_OPTS(bpf_link_create_opts, link_create_opts);
> + struct bpf_link_struct_ops *link;
> + __u32 relative_id, zero = 0;
> + int err, fd, relative_fd;
> +
> + if (!OPTS_VALID(opts, bpf_cgroup_opts))
> + return libbpf_err_ptr(-EINVAL);
> +
> + if (!bpf_map__is_struct_ops(map)) {
> + pr_warn("map '%s': can't attach non-struct_ops map\n", map->name);
> + return libbpf_err_ptr(-EINVAL);
> + }
> +
> + if (map->fd < 0) {
> + pr_warn("map '%s': can't attach BPF map without FD (was it created?)\n", map->name);
> + return libbpf_err_ptr(-EINVAL);
> + }
> +
> + if (!(map->def.map_flags & BPF_F_LINK)) {
> + pr_warn("map '%s': can't attach to cgroup without BPF_F_LINK\n", map->name);
> + return libbpf_err_ptr(-EINVAL);
> + }
> +
> + relative_id = OPTS_GET(opts, relative_id, 0);
> + relative_fd = OPTS_GET(opts, relative_fd, 0);
> +
> + if (relative_fd && relative_id) {
> + pr_warn("map '%s': relative_fd and relative_id cannot be set at the same time\n",
> + map->name);
> + return libbpf_err_ptr(-EINVAL);
> + }
> +
> + link_create_opts.cgroup.expected_revision = OPTS_GET(opts, expected_revision, 0);
> + link_create_opts.cgroup.relative_fd = relative_fd;
> + link_create_opts.cgroup.relative_id = relative_id;
> + link_create_opts.flags = OPTS_GET(opts, flags, 0);
> +
> + link = calloc(1, sizeof(*link));
> + if (!link)
> + return libbpf_err_ptr(-ENOMEM);
> +
> + err = bpf_map_update_elem(map->fd, &zero, map->st_ops->kern_vdata, 0);
> + if (err && err != -EBUSY) {
> + free(link);
> + return libbpf_err_ptr(err);
> + }
> +
> + link->link.detach = bpf_link__detach_struct_ops;
> +
> + fd = bpf_link_create(map->fd, cgroup_fd, BPF_STRUCT_OPS, &link_create_opts);
> + if (fd < 0) {
> + free(link);
> + return libbpf_err_ptr(fd);
> + }
> +
> + link->link.fd = fd;
> + link->map_fd = map->fd;
> +
> + return &link->link;
> +}
> +
> /*
> * Swap the back struct_ops of a link with a new struct_ops map.
> */
> diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h
> index b965ad571540..0e5f4e9bba41 100644
> --- a/tools/lib/bpf/libbpf.h
> +++ b/tools/lib/bpf/libbpf.h
> @@ -960,6 +960,9 @@ bpf_program__attach_cgroup_opts(const struct bpf_program *prog, int cgroup_fd,
> struct bpf_map;
>
> LIBBPF_API struct bpf_link *bpf_map__attach_struct_ops(const struct bpf_map *map);
> +LIBBPF_API struct bpf_link *bpf_map__attach_cgroup_opts(const struct bpf_map *map,
> + int cgroup_fd,
> + const struct bpf_cgroup_opts *opts);
> LIBBPF_API int bpf_link__update_map(struct bpf_link *link, const struct bpf_map *map);
>
> struct bpf_iter_attach_opts {
> diff --git a/tools/lib/bpf/libbpf.map b/tools/lib/bpf/libbpf.map
> index b731df19ae69..5131fdb979b7 100644
> --- a/tools/lib/bpf/libbpf.map
> +++ b/tools/lib/bpf/libbpf.map
> @@ -461,4 +461,5 @@ LIBBPF_1.8.0 {
> bpf_program__attach_tracing_multi;
> bpf_program__clone;
> btf__new_empty_opts;
> + bpf_map__attach_cgroup_opts;
> } LIBBPF_1.7.0;
^ permalink raw reply
* [PATCH net-next 2/2] net: phy: Add support for the Maxio MAE0621A
From: Liu Changjie @ 2026-07-14 6:46 UTC (permalink / raw)
To: Andrew Lunn, Heiner Kallweit, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, netdev
Cc: Russell King, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
devicetree, linux-kernel
In-Reply-To: <03b781d223194234bcf6e53df12880b4.1784011580644350.tspi3m-maxio-v1-cover@outlook.com>
Add exact PHY ID matching and optional 125 MHz CLKOUT configuration
for the Maxio MAE0621A Gigabit Ethernet PHY. Preserve the existing
hardware configuration when the firmware property is absent.
Signed-off-by: Liu Changjie <liucj1228@outlook.com>
---
drivers/net/phy/Kconfig | 8 +++
drivers/net/phy/Makefile | 1 +
drivers/net/phy/maxio.c | 103 +++++++++++++++++++++++++++++++++++++++
3 files changed, 112 insertions(+)
create mode 100644 drivers/net/phy/maxio.c
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index 099f25dce..32e1a035b 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -283,6 +283,14 @@ config MARVELL_88X2222_PHY
Support for the Marvell 88X2222 Dual-port Multi-speed Ethernet
Transceiver.
+config MAXIO_PHY
+ tristate "Maxio Ethernet PHYs"
+ help
+ Support for Maxio Ethernet PHYs. Currently this driver supports the
+ MAE0621A Gigabit Ethernet PHY. The driver optionally selects a 125 MHz
+ clock on the CLKOUT pin while preserving the hardware configuration on
+ boards which do not request it.
+
config MAXLINEAR_GPHY
tristate "Maxlinear Ethernet PHYs"
select POLYNOMIAL if HWMON
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index de660ae94..7fb3626f0 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -70,6 +70,7 @@ obj-$(CONFIG_MARVELL_10G_PHY) += marvell10g.o
obj-$(CONFIG_MARVELL_PHY) += marvell.o
obj-$(CONFIG_MARVELL_88Q2XXX_PHY) += marvell-88q2xxx.o
obj-$(CONFIG_MARVELL_88X2222_PHY) += marvell-88x2222.o
+obj-$(CONFIG_MAXIO_PHY) += maxio.o
obj-$(CONFIG_MAXLINEAR_GPHY) += mxl-gpy.o
obj-$(CONFIG_MAXLINEAR_86110_PHY) += mxl-86110.o
obj-y += mediatek/
diff --git a/drivers/net/phy/maxio.c b/drivers/net/phy/maxio.c
new file mode 100644
index 000000000..d2cb23895
--- /dev/null
+++ b/drivers/net/phy/maxio.c
@@ -0,0 +1,103 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Driver for Maxio Ethernet PHYs. */
+
+#include <linux/bitops.h>
+#include <linux/module.h>
+#include <linux/phy.h>
+#include <linux/property.h>
+
+#define MAXIO_MAE0621A_PHY_ID 0x7b744412
+
+#define MAXIO_PAGE_SELECT 0x1f
+#define MAXIO_MAE0621A_PHYCR2_PAGE 0xa43
+#define MAXIO_MAE0621A_PHYCR2 0x19
+#define MAXIO_MAE0621A_CLKOUT_125M BIT(11)
+#define MAXIO_MAE0621A_CLKOUT_ENABLE BIT(0)
+
+struct maxio_priv {
+ bool clk_out_125m;
+};
+
+static int maxio_read_page(struct phy_device *phydev)
+{
+ return __phy_read(phydev, MAXIO_PAGE_SELECT);
+}
+
+static int maxio_write_page(struct phy_device *phydev, int page)
+{
+ return __phy_write(phydev, MAXIO_PAGE_SELECT, page);
+}
+
+static int maxio_mae0621a_probe(struct phy_device *phydev)
+{
+ struct device *dev = &phydev->mdio.dev;
+ struct maxio_priv *priv;
+ u32 frequency;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ phydev->priv = priv;
+
+ ret = device_property_read_u32(dev, "maxio,clk-out-frequency-hz",
+ &frequency);
+ if (ret == -EINVAL)
+ return 0;
+ if (ret)
+ return ret;
+
+ if (frequency != 125000000) {
+ phydev_err(phydev, "invalid CLKOUT frequency %u\n", frequency);
+ return -EINVAL;
+ }
+
+ priv->clk_out_125m = true;
+
+ return 0;
+}
+
+static int maxio_mae0621a_config_init(struct phy_device *phydev)
+{
+ struct maxio_priv *priv = phydev->priv;
+ int ret;
+
+ if (!priv->clk_out_125m)
+ return 0;
+
+ ret = phy_modify_paged_changed(phydev, MAXIO_MAE0621A_PHYCR2_PAGE,
+ MAXIO_MAE0621A_PHYCR2,
+ MAXIO_MAE0621A_CLKOUT_ENABLE |
+ MAXIO_MAE0621A_CLKOUT_125M,
+ MAXIO_MAE0621A_CLKOUT_ENABLE |
+ MAXIO_MAE0621A_CLKOUT_125M);
+ if (ret <= 0)
+ return ret;
+
+ return genphy_soft_reset(phydev);
+}
+
+static struct phy_driver maxio_drivers[] = {
+ {
+ PHY_ID_MATCH_EXACT(MAXIO_MAE0621A_PHY_ID),
+ .name = "Maxio MAE0621A",
+ .probe = maxio_mae0621a_probe,
+ .config_init = maxio_mae0621a_config_init,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ .read_page = maxio_read_page,
+ .write_page = maxio_write_page,
+ },
+};
+module_phy_driver(maxio_drivers);
+
+static const struct mdio_device_id __maybe_unused maxio_tbl[] = {
+ { PHY_ID_MATCH_EXACT(MAXIO_MAE0621A_PHY_ID) },
+ { }
+};
+MODULE_DEVICE_TABLE(mdio, maxio_tbl);
+
+MODULE_AUTHOR("Liu Changjie <liucj1228@outlook.com>");
+MODULE_DESCRIPTION("Maxio Ethernet PHY driver");
+MODULE_LICENSE("GPL");
^ permalink raw reply related
* [PATCH net-next 1/2] dt-bindings: net: Add Maxio MAE0621A PHY
From: Liu Changjie @ 2026-07-14 6:46 UTC (permalink / raw)
To: Andrew Lunn, Heiner Kallweit, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, netdev
Cc: Russell King, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
devicetree, linux-kernel
In-Reply-To: <03b781d223194234bcf6e53df12880b4.1784011580644350.tspi3m-maxio-v1-cover@outlook.com>
Document the MAE0621A PHY ID and its optional 125 MHz CLKOUT
setting. Boards which do not provide the property retain the hardware
strap or bootloader configuration.
Only the 125 MHz setting is exposed because it is the only setting
exercised on hardware.
Signed-off-by: Liu Changjie <liucj1228@outlook.com>
---
.../bindings/net/maxio,mae0621a.yaml | 38 +++++++++++++++++++
.../devicetree/bindings/vendor-prefixes.yaml | 2 +
2 files changed, 40 insertions(+)
create mode 100644 Documentation/devicetree/bindings/net/maxio,mae0621a.yaml
diff --git a/Documentation/devicetree/bindings/net/maxio,mae0621a.yaml b/Documentation/devicetree/bindings/net/maxio,mae0621a.yaml
new file mode 100644
index 000000000..f1ea54583
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/maxio,mae0621a.yaml
@@ -0,0 +1,38 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/maxio,mae0621a.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Maxio MAE0621A Ethernet PHY
+
+maintainers:
+ - Liu Changjie <liucj1228@outlook.com>
+
+allOf:
+ - $ref: ethernet-phy.yaml#
+
+properties:
+ compatible:
+ const: ethernet-phy-id7b74.4412
+
+ maxio,clk-out-frequency-hz:
+ description:
+ Selects a 125 MHz clock on the CLKOUT pin. If the property is absent,
+ the hardware strap or bootloader configuration is preserved.
+ const: 125000000
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ mdio {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ ethernet-phy@1 {
+ compatible = "ethernet-phy-id7b74.4412";
+ reg = <1>;
+ maxio,clk-out-frequency-hz = <125000000>;
+ };
+ };
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml
index 396044f36..eeaf0b7a1 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
@@ -1013,6 +1013,8 @@ patternProperties:
description: MaxBotix Inc.
"^maxim,.*":
description: Maxim Integrated Products
+ "^maxio,.*":
+ description: Maxio Technology (Hangzhou) Co., Ltd.
"^maxlinear,.*":
description: MaxLinear Inc.
"^maxtor,.*":
^ permalink raw reply related
* [PATCH net-next 0/2] net: phy: Add Maxio MAE0621A support
From: Liu Changjie @ 2026-07-14 6:46 UTC (permalink / raw)
To: Andrew Lunn, Heiner Kallweit, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, netdev
Cc: Russell King, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
devicetree, linux-kernel
Add a binding and PHY driver for the Maxio MAE0621A Gigabit Ethernet
PHY. The driver matches the exact PHY ID and optionally selects a
125 MHz clock on CLKOUT. When the property is absent, the driver
preserves the hardware strap or bootloader setting.
This was tested on the LCKFB Taishan Pi 3M. With the PHY's default
CLKOUT configuration, the RK3576 GMAC negotiated a 1 Gbit/s link but
the data path did not work. A read-modify-write of page 0xa43,
register 0x19, setting bits 0 and 11 produced a readback value of
0x0823 and restored the data path without changing the RGMII delays.
The readback also confirmed that the unrelated set bits were
preserved.
Only the 125 MHz setting has been exercised on hardware and only that
value is exposed by this series. The 0 and 25 MHz choices from the
initial draft have been removed.
Public documentation for the MAE0621A register is not available. The
exact definitions of page 0xa43, register 0x19 bits 0 and 11, their
reset defaults, and whether a soft reset is required after changing
them still need confirmation from Maxio. The register programming in
this series is therefore supported by the hardware A/B result, not by
a public datasheet citation.
Hardware tests with the 125 MHz setting:
- 1 Gbit/s, full duplex link negotiation
- 100 ICMP packets in each direction with no loss
- 936/934 Mbit/s host-to-board iperf3, no retransmits
- 943/941 Mbit/s board-to-host iperf3, no retransmits
- link unplug/replug and a normal reboot, followed by another 100 ICMP
packets in each direction with no loss
- no RX errors, missed packets, carrier errors or collisions
Static tests on net-next commit f6f3b36c15ed:
- dt_binding_check with dtschema 2026.6
- W=1 arm64 defconfig build of drivers/net/phy/maxio.o with GCC 10.3
- W=1 arm64 allmodconfig build of drivers/net/phy/maxio.o with GCC 10.3
- git diff --check and checkpatch.pl
Liu Changjie (2):
dt-bindings: net: Add Maxio MAE0621A PHY
net: phy: Add support for the Maxio MAE0621A
.../bindings/net/maxio,mae0621a.yaml | 38 +++++++
.../devicetree/bindings/vendor-prefixes.yaml | 2 +
drivers/net/phy/Kconfig | 8 ++
drivers/net/phy/Makefile | 1 +
drivers/net/phy/maxio.c | 103 ++++++++++++++++++
5 files changed, 152 insertions(+)
create mode 100644 Documentation/devicetree/bindings/net/maxio,mae0621a.yaml
create mode 100644 drivers/net/phy/maxio.c
base-commit: f6f3b36c15ed44de1fbb44e645e4fae8c4a4453e
--
2.55.0
^ permalink raw reply
* Re: [PATCH net] net/x25: fix use-after-free in x25_kill_by_neigh()
From: Martin Schiller @ 2026-07-14 6:42 UTC (permalink / raw)
To: David Lee
Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Dominik 'Disconnect3d' Czarnota, linux-x25,
netdev, linux-kernel, stable
In-Reply-To: <20260713104752.241175-1-david.lee@trailofbits.com>
On 2026-07-13 12:47, David Lee wrote:
> x25_kill_by_neigh() walks the global X.25 socket list looking for
> sockets
> attached to a terminating neighbour. x25_list_lock protects list
> membership
> while the lookup is in progress, but it does not pin a socket's
> lifetime
> after the lock is dropped.
>
> The function currently drops x25_list_lock before calling lock_sock(s).
> A
> concurrent close can run x25_release(), remove the same socket from
> x25_list, and drop the last socket reference in that window. The
> neighbour
> teardown path can then lock or inspect a freed struct sock/struct
> x25_sock.
>
> Take sock_hold(s) while x25_list_lock still proves that the list entry
> is
> live, then drop the temporary reference after the socket has been
> locked,
> rechecked, and released. Recheck x25_sk(s)->neighbour after
> lock_sock(),
> because another path may have disconnected the socket before this path
> acquired the socket lock. Restart the list walk after each disconnect
> because the list lock was dropped and the previous iterator state may
> no
> longer be valid.
>
> A QEMU/KASAN run against origin/master reproduced a slab-use-after-free
> in
> x25_kill_by_neigh().
>
> Fixes: 7781607938c8 ("net/x25: Fix null-ptr-deref caused by
> x25_disconnect")
> Cc: stable@vger.kernel.org
> Signed-off-by: David Lee <david.lee@trailofbits.com>
> Assisted-by: Codex:gpt-5.5
> ---
> Trail of Bits has a reproducer that triggers kernel panic
> demonstrating the bug, and can share it if needed.
>
> net/x25/af_x25.c | 8 ++++++--
> 1 file changed, 6 insertions(+), 2 deletions(-)
>
> diff --git a/net/x25/af_x25.c b/net/x25/af_x25.c
> index c31d2af5dd22..8aae9273b7c1 100644
> --- a/net/x25/af_x25.c
> +++ b/net/x25/af_x25.c
> @@ -1768,15 +1768,19 @@ void x25_kill_by_neigh(struct x25_neigh *nb)
> {
> struct sock *s;
>
> +again:
> write_lock_bh(&x25_list_lock);
>
> sk_for_each(s, &x25_list) {
> if (x25_sk(s)->neighbour == nb) {
> + sock_hold(s);
> write_unlock_bh(&x25_list_lock);
> lock_sock(s);
> - x25_disconnect(s, ENETUNREACH, 0, 0);
> + if (x25_sk(s)->neighbour == nb)
> + x25_disconnect(s, ENETUNREACH, 0, 0);
> release_sock(s);
> - write_lock_bh(&x25_list_lock);
> + sock_put(s);
> + goto again;
> }
> }
> write_unlock_bh(&x25_list_lock);
LGTM, Thanks.
Acked-by: Martin Schiller <ms@dev.tdt.de>
^ permalink raw reply
* [PATCH iwl-net v1] ice: fix use-after-free in dynamic port cleanup
From: xuanqiang.luo @ 2026-07-14 6:39 UTC (permalink / raw)
To: intel-wired-lan
Cc: anthony.l.nguyen, przemyslaw.kitszel, andrew+netdev,
sridhar.samudrala, wojciech.drewek, piotr.raczynski,
michal.swiatkowski, jacob.e.keller, netdev, Xuanqiang Luo, stable
From: Xuanqiang Luo <luoxuanqiang@kylinos.cn>
ice_dealloc_dynamic_port() uses dyn_port->vsi->idx to erase the dynamic
port from pf->dyn_ports. However, it frees the VSI before reading the
index for the erase, resulting in a use-after-free.
Follow the reverse of the allocation order in ice_alloc_dynamic_port()
by erasing the xarray entry before freeing the VSI.
Fixes: eda69d654c7e ("ice: add basic devlink subfunctions support")
Cc: stable@vger.kernel.org
Signed-off-by: Xuanqiang Luo <luoxuanqiang@kylinos.cn>
---
drivers/net/ethernet/intel/ice/devlink/port.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/intel/ice/devlink/port.c b/drivers/net/ethernet/intel/ice/devlink/port.c
index 2a2e56777f9f7..3ede246490027 100644
--- a/drivers/net/ethernet/intel/ice/devlink/port.c
+++ b/drivers/net/ethernet/intel/ice/devlink/port.c
@@ -590,8 +590,8 @@ static void ice_dealloc_dynamic_port(struct ice_dynamic_port *dyn_port)
xa_erase(&pf->sf_nums, devlink_port->attrs.pci_sf.sf);
ice_eswitch_detach_sf(pf, dyn_port);
- ice_vsi_free(dyn_port->vsi);
xa_erase(&pf->dyn_ports, dyn_port->vsi->idx);
+ ice_vsi_free(dyn_port->vsi);
kfree(dyn_port);
}
--
2.43.0
^ permalink raw reply related
* Re: [PATCH bpf-next v3 10/15] bpf: Allow all struct_ops to use bpf_dynptr_from_skb()
From: Emil Tsalapatis @ 2026-07-14 6:23 UTC (permalink / raw)
To: Amery Hung, bpf
Cc: netdev, alexei.starovoitov, andrii, daniel, eddyz87, memxor,
martin.lau, shakeel.butt, roman.gushchin, kuniyu, kerneljasonxing,
kernel-team
In-Reply-To: <20260706171918.317102-11-ameryhung@gmail.com>
On Mon Jul 6, 2026 at 1:19 PM EDT, Amery Hung wrote:
> bpf_dynptr_from_skb() was only made available to bpf_qdisc, so far the
> only struct_ops type that needs to read an skb. The upcoming bpf_tcp_ops
> header-option hooks (parse_hdr/write_hdr_opt) also want to access the TCP
> options of an skb through a dynptr.
>
> All struct_ops programs share BPF_PROG_TYPE_STRUCT_OPS, so register
> bpf_kfunc_set_skb (which holds bpf_dynptr_from_skb) for that program type
> once, instead of per struct_ops. This makes bpf_dynptr_from_skb()
> available to bpf_tcp_ops and any future struct_ops.
>
> With the kfunc now provided to all of struct_ops, the bpf_qdisc-specific
> registration becomes redundant and is dropped: bpf_qdisc_kfunc_filter()
> only constrains kfuncs listed in qdisc_kfunc_ids, so removing
> bpf_dynptr_from_skb from that set (and from qdisc_common_kfunc_set) lets
> it fall through the filter unchanged, and bpf_qdisc keeps access via the
> generic struct_ops registration.
>
> Widening the registration is safe: a struct_ops that does not receive an
> skb in its context has nothing to pass to the helper.
>
> Signed-off-by: Amery Hung <ameryhung@gmail.com>
Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>
> ---
> net/core/filter.c | 1 +
> net/sched/bpf_qdisc.c | 2 --
> 2 files changed, 1 insertion(+), 2 deletions(-)
>
> diff --git a/net/core/filter.c b/net/core/filter.c
> index 4f5cbcac3e78..05f492c215e0 100644
> --- a/net/core/filter.c
> +++ b/net/core/filter.c
> @@ -12635,6 +12635,7 @@ static int __init bpf_kfunc_init(void)
> ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_LWT_SEG6LOCAL, &bpf_kfunc_set_skb);
> ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_NETFILTER, &bpf_kfunc_set_skb);
> ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_TRACING, &bpf_kfunc_set_skb);
> + ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_STRUCT_OPS, &bpf_kfunc_set_skb);
> ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_SCHED_CLS, &bpf_kfunc_set_skb_meta);
> ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_SCHED_ACT, &bpf_kfunc_set_skb_meta);
> ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_XDP, &bpf_kfunc_set_xdp);
> diff --git a/net/sched/bpf_qdisc.c b/net/sched/bpf_qdisc.c
> index 098ca02aed89..5691c13781a8 100644
> --- a/net/sched/bpf_qdisc.c
> +++ b/net/sched/bpf_qdisc.c
> @@ -280,7 +280,6 @@ BTF_KFUNCS_START(qdisc_kfunc_ids)
> BTF_ID_FLAGS(func, bpf_skb_get_hash)
> BTF_ID_FLAGS(func, bpf_kfree_skb, KF_RELEASE)
> BTF_ID_FLAGS(func, bpf_qdisc_skb_drop, KF_RELEASE)
> -BTF_ID_FLAGS(func, bpf_dynptr_from_skb)
> BTF_ID_FLAGS(func, bpf_qdisc_watchdog_schedule)
> BTF_ID_FLAGS(func, bpf_qdisc_init_prologue)
> BTF_ID_FLAGS(func, bpf_qdisc_reset_destroy_epilogue)
> @@ -290,7 +289,6 @@ BTF_KFUNCS_END(qdisc_kfunc_ids)
> BTF_SET_START(qdisc_common_kfunc_set)
> BTF_ID(func, bpf_skb_get_hash)
> BTF_ID(func, bpf_kfree_skb)
> -BTF_ID(func, bpf_dynptr_from_skb)
> BTF_SET_END(qdisc_common_kfunc_set)
>
> BTF_SET_START(qdisc_enqueue_kfunc_set)
^ permalink raw reply
* Re: [PATCH bpf-next v3 09/15] bpf: Add infrastructure to support attaching struct_ops to cgroups
From: Emil Tsalapatis @ 2026-07-14 6:21 UTC (permalink / raw)
To: Amery Hung, bpf
Cc: netdev, alexei.starovoitov, andrii, daniel, eddyz87, memxor,
martin.lau, shakeel.butt, roman.gushchin, kuniyu, kerneljasonxing,
kernel-team
In-Reply-To: <20260706171918.317102-10-ameryhung@gmail.com>
On Mon Jul 6, 2026 at 1:19 PM EDT, Amery Hung wrote:
> From: Martin KaFai Lau <martin.lau@kernel.org>
>
> This patch adds necessary infrastructure to attach a struct_ops
> map to a cgroup. The initial need was to support migrating
> the legacy BPF_PROG_TYPE_SOCK_OPS to a struct_ops.
> Recently, there are other struct_ops use cases that
> need to attach struct_ops to a cgroup. For example,
> the recent BPF OOM and memcg discussion in LSFMMBPF 2026.
>
> The motivation is to create a consistent expectation
> for attaching struct_ops to cgroup instead of each subsystem
> creating its own infrastructure. This logic includes
> hierarchy expectation, ordering expectation,
> attachment API, and rcu gp.
>
> There is already an existing implementation for attaching
> multiple bpf progs to a cgroup. There are also tools
> built around it for querying. Attaching a struct_ops map
> (which is a group of bpf programs) could also adhere to
> a similar API and potentially reuse most of the existing
> implementation.
>
> A couple of ideas have been tried. One of them
> is to use mprog.c. In terms of the amount of changes,
> I eventually came to the same conclusion as in
> commit 120933984460 ("bpf: Implement mprog API on top of existing cgroup progs").
> I then shifted the focus to reusing the current
> {update,compute,activate,purge}_effective_progs() which has
> the main logic that implements the mprog API.
>
> Since then, I tried to add a 'struct cgroup *cgroup' member
> to the existing 'struct bpf_struct_ops_link' and link_create
> will create a 'struct bpf_struct_ops_link' object to be stored
> in the pl->link. This turns out to have more changes on
> both cgroup.c and bpf_struct_ops.c than I like.
>
> This patch directly reuses the 'struct bpf_cgroup_link' which
> cgroup.c already understands. Add 'struct bpf_map *map'
> to 'struct bpf_cgroup_link'. In the future, as more subsystems
> are extended by struct_ops, we may consider to make
> 'struct bpf_map *map' as a primary citizen of a link
> like 'struct bpf_prog *prog' and directly add
> 'struct bpf_map *map' to the generic 'struct bpf_link'.
>
> The pl->link could be the traditional 'prog' link or the
> new 'map' link. The places that need to handle them differently
> have already been refactored into the new prog_list_*() added in
> the earlier patch. In those new prog_list_*(), this patch will
> check "pl->link && pl->link->map", learn that it is a 'map' link
> and handle it correctly.
>
> The bpf_prog_array also needs to handle that its item can store
> the traditional 'prog' or it can store a struct_ops map.
> The places that need to handle them differently have also
> been refactored into the new bpf_cgroup_array_*() added
> in the earlier patch. The two differences are:
> - different sentinel (dummy_bpf_prog in prog vs cfi_stub in struct_ops)
> - the array for struct_ops may need to go through different
> rcu gp.
> The bpf_cgroup_array_*() functions use the cgroup_bpf_attach_type (ie atype)
> to distinguish the array is storing prog or storing struct_ops map.
>
> This patch also implements a separate struct bpf_link_ops
> "cgroup_struct_ops_link_ops" to have a separate link_ops implementation
> that only handles the cgroup's struct_ops link.
>
> Questions:
> - Although this patch did not change it, it is not obvious to me how
> the replace_effective_progs() and purge_effective_progs() handle
> cases when there are existing BPF_F_PREORDER progs attached
> in the hlist.
>
> Misc notes:
> - CGROUP_TCP_SOCK_OPS is added to the 'enum cgroup_bpf_attach_type'.
> The actual implementation of the tcp_bpf_ops (a struct_ops)
> will be added in the next patch.
>
> - free_after_mult_rcu_gp is added to 'struct bpf_struct_ops' such that
> the bpf_prog_array can have a mix of sleepable and
> non-sleepable prog in a struct_ops. This can tell
> how the bpf_prog_array should be freed.
>
> - For a struct_ops that supports cgroup attachment, it does not need to
> implement its own reg/unreg function. reg/unreg to a cgroup is
> done by the common infrastructure added in this patch.
>
> - The cgroup's struct_ops link only supports BPF_F_ALLOW_MULTI.
> This is enforced internally in cgroup_bpf_struct_ops_attach.
> This should be consistent with the current prog's link
> behavior in cgroup_bpf_link_attach.
>
> In the future, we may allow each subsystem to choose differently.
>
> - A cgroup_atype member is added to 'struct bpf_struct_ops'.
> When a subsystem struct_ops needs to support cgroup attachment,
> it needs to add a value to 'enum cgroup_bpf_attach_type'
> and then assign it to the newly added cgroup_atype member
> in the bpf_struct_ops.
>
> - During LINK_CREATE in syscall, the patch uses the same
> BPF_STRUCT_OPS (in attr->link_create.attach_type).
> The bpf_struct_ops_link_create learns the map and
> from the map it learns the st_ops. If the st_ops->cgroup_atype
> is not 0, it will create a cgroup's link.
>
> - When a subsystem registers a struct_ops that supports cgroup
> attachment, the struct_ops infrastructure will also ask the
> cgroup infrastructure to remember a few things. This is done
> by calling cgroup_bpf_struct_ops_register().
>
> Signed-off-by: Martin KaFai Lau <martin.lau@kernel.org>
> Signed-off-by: Amery Hung <ameryhung@gmail.com>
Hi Amery,
> ---
> include/linux/bpf-cgroup-defs.h | 1 +
> include/linux/bpf-cgroup.h | 28 +++
> include/linux/bpf.h | 19 +-
> include/uapi/linux/bpf.h | 4 +-
> kernel/bpf/bpf_struct_ops.c | 29 +++
> kernel/bpf/btf.c | 23 +-
> kernel/bpf/cgroup.c | 375 ++++++++++++++++++++++++++++++--
> kernel/bpf/syscall.c | 1 +
> tools/include/uapi/linux/bpf.h | 4 +-
> 9 files changed, 463 insertions(+), 21 deletions(-)
>
> diff --git a/include/linux/bpf-cgroup-defs.h b/include/linux/bpf-cgroup-defs.h
> index c9e6b26abab6..0147b8bec973 100644
> --- a/include/linux/bpf-cgroup-defs.h
> +++ b/include/linux/bpf-cgroup-defs.h
> @@ -47,6 +47,7 @@ enum cgroup_bpf_attach_type {
> CGROUP_INET6_GETSOCKNAME,
> CGROUP_UNIX_GETSOCKNAME,
> CGROUP_INET_SOCK_RELEASE,
> + CGROUP_TCP_SOCK_OPS,
> CGROUP_LSM_START,
> CGROUP_LSM_END = CGROUP_LSM_START + CGROUP_LSM_NUM - 1,
> MAX_CGROUP_BPF_ATTACH_TYPE
> diff --git a/include/linux/bpf-cgroup.h b/include/linux/bpf-cgroup.h
> index 4d0cc65976a1..8a75a6cd7309 100644
> --- a/include/linux/bpf-cgroup.h
> +++ b/include/linux/bpf-cgroup.h
> @@ -100,6 +100,8 @@ struct bpf_cgroup_storage {
> struct bpf_cgroup_link {
> struct bpf_link link;
> struct cgroup *cgroup;
> + struct bpf_map *map;
> + wait_queue_head_t wait_hup;
> };
>
> struct bpf_prog_list {
> @@ -110,6 +112,18 @@ struct bpf_prog_list {
> u32 flags;
> };
>
> +#define bpf_cgroup_struct_ops_foreach(var, item, cgrp, atype) \
> + for (item = rcu_dereference((cgrp)->bpf.effective[atype])->items;\
> + ((var) = READ_ONCE(item->kdata)); \
> + item++)
> +
> +static inline bool cgroup_bpf_is_struct_ops_atype(enum cgroup_bpf_attach_type atype)
> +{
> + return atype == CGROUP_TCP_SOCK_OPS;
> +}
> +void cgroup_bpf_struct_ops_register(int atype, u32 type_id, void *cfi_stubs, bool mult_trace);
> +int cgroup_bpf_struct_ops_attach(struct bpf_map *map, const union bpf_attr *attr);
> +
> void __init cgroup_bpf_lifetime_notifier_init(void);
>
> int __cgroup_bpf_run_filter_skb(struct sock *sk,
> @@ -479,6 +493,20 @@ static inline int bpf_percpu_cgroup_storage_update(struct bpf_map *map,
> return 0;
> }
>
> +static inline bool cgroup_bpf_is_struct_ops_atype(int atype)
> +{
> + return false;
> +}
> +static inline void cgroup_bpf_struct_ops_register(int atype, u32 type_id, void *cfi_stubs,
> + bool mult_trace)
> +{
> +}
> +static inline int cgroup_bpf_struct_ops_attach(struct bpf_map *map,
> + const union bpf_attr *attr)
> +{
> + return -EOPNOTSUPP;
> +}
> +
> #define cgroup_bpf_enabled(atype) (0)
> #define BPF_CGROUP_RUN_SA_PROG_LOCK(sk, uaddr, uaddrlen, atype, t_ctx) ({ 0; })
> #define BPF_CGROUP_RUN_SA_PROG(sk, uaddr, uaddrlen, atype) ({ 0; })
> diff --git a/include/linux/bpf.h b/include/linux/bpf.h
> index e371a4733135..df95ae690da5 100644
> --- a/include/linux/bpf.h
> +++ b/include/linux/bpf.h
> @@ -2100,11 +2100,18 @@ struct btf_member;
> * unloaded while in use.
> * @name: The name of the struct bpf_struct_ops object.
> * @func_models: Func models
> + * @cgroup_atype: A value in enum cgroup_bpf_attach_type for cgroup attachment.
> + * 0 means the struct_ops type does not support cgroup attachment.
> + * If cgroup_atype is non-zero, the @reg and @unreg must be NULL
> + * because the attachment/detachment will be handled by the bpf core.
> * @free_after_tasks_rcu_gp: Set to true if it needs the bpf core to wait for
> * a tasks_rcu gp before freeing the struct_ops map
> * and its progs. It is unnecessary if the @unreg
> * has waited for the correct rcu gp or the @unreg
> * has ensured all struct_ops prog has finished running.
> + * @free_after_mult_rcu_gp: Same as @free_after_tasks_rcu_gp but waiting for
> + * both tasks_trace_rcu and regular rcu grace period.
> + * It is usually needed if the struct_ops has sleepable prog.
> */
> struct bpf_struct_ops {
> const struct bpf_verifier_ops *verifier_ops;
> @@ -2123,7 +2130,9 @@ struct bpf_struct_ops {
> struct module *owner;
> const char *name;
> struct btf_func_model func_models[BPF_STRUCT_OPS_MAX_NR_MEMBERS];
> + int cgroup_atype;
> bool free_after_tasks_rcu_gp;
> + bool free_after_mult_rcu_gp;
> };
>
> /* Every member of a struct_ops type has an instance even a member is not
> @@ -2258,6 +2267,7 @@ void *bpf_struct_ops_map_cfi_stubs(struct bpf_map *map);
> bool bpf_struct_ops_valid_to_reg(struct bpf_map *map);
> int bpf_struct_ops_link_update_check(struct bpf_map *new_map, struct bpf_map *old_map,
> struct bpf_map *expected_old_map);
> +int bpf_struct_ops_map_cgroup_atype(struct bpf_map *map);
>
> #ifdef CONFIG_NET
> /* Define it here to avoid the use of forward declaration */
> @@ -2330,6 +2340,10 @@ static inline u32 bpf_struct_ops_kdata_map_id(void *kdata)
> {
> return 0;
> }
> +static inline int bpf_struct_ops_map_cgroup_atype(struct bpf_map *map)
> +{
> + return 0;
> +}
> static inline void *bpf_struct_ops_map_cfi_stubs(struct bpf_map *map)
> {
> return NULL;
> @@ -2519,7 +2533,10 @@ u64 bpf_event_output(struct bpf_map *map, u64 flags, void *meta, u64 meta_size,
> * since other cpus are walking the array of pointers in parallel.
> */
> struct bpf_prog_array_item {
> - struct bpf_prog *prog;
> + union {
> + struct bpf_prog *prog;
> + void *kdata;
> + };
> union {
> struct bpf_cgroup_storage *cgroup_storage[MAX_BPF_CGROUP_STORAGE_TYPE];
> u64 bpf_cookie;
> diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
> index c91b5a4bda03..c982801ec7db 100644
> --- a/include/uapi/linux/bpf.h
> +++ b/include/uapi/linux/bpf.h
> @@ -1756,7 +1756,7 @@ union bpf_attr {
> __u32 prog_cnt;
> __u32 count;
> };
> - __u32 :32;
> + __u32 type_id;
> /* output: per-program attach_flags.
> * not allowed to be set during effective query.
> */
> @@ -6818,6 +6818,8 @@ struct bpf_link_info {
> } xdp;
> struct {
> __u32 map_id;
> + __u32 :32;
> + __u64 cgroup_id;
> } struct_ops;
> struct {
> __u32 pf;
> diff --git a/kernel/bpf/bpf_struct_ops.c b/kernel/bpf/bpf_struct_ops.c
> index 3d650a7e9f68..06d72cdca601 100644
> --- a/kernel/bpf/bpf_struct_ops.c
> +++ b/kernel/bpf/bpf_struct_ops.c
> @@ -13,6 +13,7 @@
> #include <linux/btf_ids.h>
> #include <linux/rcupdate_wait.h>
> #include <linux/poll.h>
> +#include <linux/bpf-cgroup.h>
>
> struct bpf_struct_ops_value {
> struct bpf_struct_ops_common_value common;
> @@ -1076,6 +1077,11 @@ static struct bpf_map *bpf_struct_ops_map_alloc(union bpf_attr *attr)
> goto errout;
> }
>
> + if (st_ops_desc->st_ops->cgroup_atype && !(attr->map_flags & BPF_F_LINK)) {
> + ret = -EOPNOTSUPP;
> + goto errout;
> + }
> +
> vt = st_ops_desc->value_type;
> if (attr->value_size != vt->size) {
> ret = -EINVAL;
> @@ -1116,6 +1122,7 @@ static struct bpf_map *bpf_struct_ops_map_alloc(union bpf_attr *attr)
>
> mutex_init(&st_map->lock);
> bpf_map_init_from_attr(map, attr);
> + map->free_after_mult_rcu_gp = st_ops_desc->st_ops->free_after_mult_rcu_gp;
> map->free_after_rcu_gp = true;
>
> return map;
> @@ -1244,6 +1251,14 @@ void *bpf_struct_ops_map_kdata(struct bpf_map *map)
> return st_map->kvalue.data;
> }
>
> +int bpf_struct_ops_map_cgroup_atype(struct bpf_map *map)
> +{
> + struct bpf_struct_ops_map *st_map;
> +
> + st_map = container_of(map, struct bpf_struct_ops_map, map);
> + return st_map->st_ops_desc->st_ops->cgroup_atype;
> +}
> +
> void *bpf_struct_ops_map_cfi_stubs(struct bpf_map *map)
> {
> struct bpf_struct_ops_map *st_map;
> @@ -1419,6 +1434,7 @@ int bpf_struct_ops_link_create(union bpf_attr *attr)
> struct bpf_link_primer link_primer;
> struct bpf_struct_ops_map *st_map;
> struct bpf_map *map;
> + int cgroup_atype;
> int err;
>
> map = bpf_map_get(attr->link_create.map_fd);
> @@ -1432,6 +1448,19 @@ int bpf_struct_ops_link_create(union bpf_attr *attr)
> goto err_out;
> }
>
> + cgroup_atype = st_map->st_ops_desc->st_ops->cgroup_atype;
> + if (cgroup_atype) {
> + err = cgroup_bpf_struct_ops_attach(map, attr);
> + bpf_map_put(map);
> + return err;
> + }
> +
> + if (memchr_inv(&attr->link_create.cgroup, 0, sizeof(attr->link_create.cgroup)) ||
> + attr->link_create.target_fd) {
> + err = -EINVAL;
> + goto err_out;
> + }
> +
> link = kzalloc_obj(*link, GFP_USER);
> if (!link) {
> err = -ENOMEM;
> diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
> index 64572f85edc8..d591f306ace5 100644
> --- a/kernel/bpf/btf.c
> +++ b/kernel/bpf/btf.c
> @@ -20,6 +20,7 @@
> #include <linux/btf.h>
> #include <linux/btf_ids.h>
> #include <linux/bpf.h>
> +#include <linux/bpf-cgroup.h>
> #include <linux/bpf_lsm.h>
> #include <linux/skmsg.h>
> #include <linux/perf_event.h>
> @@ -9836,6 +9837,7 @@ btf_add_struct_ops(struct btf *btf, struct bpf_struct_ops *st_ops,
> struct bpf_verifier_log *log)
> {
> struct btf_struct_ops_tab *tab, *new_tab;
> + int cgroup_atype;
> int i, err;
>
> tab = btf->struct_ops_tab;
> @@ -9847,8 +9849,10 @@ btf_add_struct_ops(struct btf *btf, struct bpf_struct_ops *st_ops,
> btf->struct_ops_tab = tab;
> }
>
> + cgroup_atype = st_ops->cgroup_atype;
> for (i = 0; i < tab->cnt; i++)
> - if (tab->ops[i].st_ops == st_ops)
> + if (tab->ops[i].st_ops == st_ops ||
> + (cgroup_atype && cgroup_atype == tab->ops[i].st_ops->cgroup_atype))
> return -EEXIST;
>
> if (tab->cnt == tab->capacity) {
> @@ -9868,6 +9872,23 @@ btf_add_struct_ops(struct btf *btf, struct bpf_struct_ops *st_ops,
> if (err)
> return err;
>
> + if (cgroup_atype) {
> + if (!cgroup_bpf_is_struct_ops_atype(cgroup_atype) ||
Can we add a comment here as to why free_after_tasks_rcu_gp is always
invalid here (which IIUC is because it's
free_after_mult_rcu_gp)?
> + st_ops->reg || st_ops->unreg || st_ops->free_after_tasks_rcu_gp) {
> + bpf_struct_ops_desc_release(&tab->ops[btf->struct_ops_tab->cnt]);
> + return -EINVAL;
> + }
> +
> + /* There is no need to unregister from cgroup when the
> + * btf_free(). No struct_ops map and its cgroup link
> + * can be created once its btf is gone.
> + */
Network-style comment, should be regular kernel-style.
In terms of the content: What happens if the struct_ops are in a module,
then we load/unload/reload? IIUC even if the signatures are identical,
the btf_id could be different so it's not like we can always reuse the
previous cgroup registration.
> + cgroup_bpf_struct_ops_register(cgroup_atype,
> + tab->ops[btf->struct_ops_tab->cnt].type_id,
> + st_ops->cfi_stubs,
> + st_ops->free_after_mult_rcu_gp);
> + }
> +
> btf->struct_ops_tab->cnt++;
>
> return 0;
> diff --git a/kernel/bpf/cgroup.c b/kernel/bpf/cgroup.c
> index 081d81de1816..745e826c9f61 100644
> --- a/kernel/bpf/cgroup.c
> +++ b/kernel/bpf/cgroup.c
> @@ -24,6 +24,29 @@
> DEFINE_STATIC_KEY_ARRAY_FALSE(cgroup_bpf_enabled_key, MAX_CGROUP_BPF_ATTACH_TYPE);
> EXPORT_SYMBOL(cgroup_bpf_enabled_key);
>
> +static u32 struct_ops_type_id[MAX_CGROUP_BPF_ATTACH_TYPE];
> +static void *struct_ops_cfi_stubs[MAX_CGROUP_BPF_ATTACH_TYPE];
> +static bool struct_ops_mult_rcu[MAX_CGROUP_BPF_ATTACH_TYPE];
Can we make a struct out of those? Right now we're carrying them loose
into the module even though every index corresponds to a single
registered struct_oips.
> +
> +void cgroup_bpf_struct_ops_register(int atype, u32 type_id, void *cfi_stubs, bool mult_rcu)
> +{
> + struct_ops_type_id[atype] = type_id;
> + struct_ops_cfi_stubs[atype] = cfi_stubs;
> + struct_ops_mult_rcu[atype] = mult_rcu;
> +}
> +
> +static enum cgroup_bpf_attach_type find_atype_by_struct_ops_id(u32 type_id)
> +{
> + enum cgroup_bpf_attach_type atype;
> +
> + for (atype = 0; atype < MAX_CGROUP_BPF_ATTACH_TYPE; atype++) {
> + if (cgroup_bpf_is_struct_ops_atype(atype) &&
> + struct_ops_type_id[atype] == type_id)
> + return atype;
> + }
> + return CGROUP_BPF_ATTACH_TYPE_INVALID;
> +}
> +
> /*
> * cgroup bpf destruction makes heavy use of work items and there can be a lot
> * of concurrent destructions. Use a separate workqueue so that cgroup bpf
> @@ -306,6 +329,19 @@ static void bpf_cgroup_storages_link(struct bpf_cgroup_storage *storages[],
> bpf_cgroup_storage_link(storages[stype], cgrp, attach_type);
> }
>
> +static void cgroup_struct_ops_link_detach_wake(struct bpf_cgroup_link *link, bool wake_poll)
> +{
> + cgroup_put(link->cgroup);
> + link->cgroup = NULL;
> +
> + bpf_map_put(link->map);
> + /* READ_ONCE in cgroup_struct_ops_link_poll */
> + WRITE_ONCE(link->map, NULL);
> +
> + if (wake_poll)
> + wake_up_interruptible_poll(&link->wait_hup, EPOLLHUP);
> +}
> +
> /* Called when bpf_cgroup_link is auto-detached from dying cgroup.
> * It drops cgroup and bpf_prog refcounts, and marks bpf_link as defunct. It
> * doesn't free link memory, which will eventually be done by bpf_link's
> @@ -313,21 +349,37 @@ static void bpf_cgroup_storages_link(struct bpf_cgroup_storage *storages[],
> */
> static void bpf_cgroup_link_auto_detach(struct bpf_cgroup_link *link)
> {
> - if (link->link.prog->expected_attach_type == BPF_LSM_CGROUP)
> - bpf_trampoline_unlink_cgroup_shim(link->link.prog);
> - cgroup_put(link->cgroup);
> - link->cgroup = NULL;
> + if (link->map) {
> + cgroup_struct_ops_link_detach_wake(link, true);
> + } else {
> + if (link->link.prog->expected_attach_type == BPF_LSM_CGROUP)
> + bpf_trampoline_unlink_cgroup_shim(link->link.prog);
> + cgroup_put(link->cgroup);
> + link->cgroup = NULL;
> + }
Nit: If we write this as:
if (link->map) {
cgroup_struct_ops_link_detach_wake(link, true);
return
}
it's slightly less churn.
> +}
> +
> +static void bpf_cgroup_array_free_rcu(struct rcu_head *rcu)
> +{
> + kfree(container_of(rcu, struct bpf_prog_array, rcu));
> }
>
> -static void bpf_cgroup_array_free(struct bpf_prog_array *array)
> +static void bpf_cgroup_array_free(struct bpf_prog_array *array,
> + enum cgroup_bpf_attach_type atype)
> {
> if (!array || array == &bpf_empty_prog_array)
> return;
> - kfree_rcu(array, rcu);
> + if (struct_ops_mult_rcu[atype])
> + /* RCU tasks trace grace period implies RCU grace period. */
> + call_rcu_tasks_trace(&array->rcu, bpf_cgroup_array_free_rcu);
> + else
> + kfree_rcu(array, rcu);
> }
>
> static void *bpf_cgroup_array_dummy(enum cgroup_bpf_attach_type atype)
> {
> + if (cgroup_bpf_is_struct_ops_atype(atype))
> + return struct_ops_cfi_stubs[atype];
> return bpf_prog_dummy();
> }
>
> @@ -355,7 +407,12 @@ static int bpf_cgroup_array_copy_to_user(struct bpf_prog_array *array,
> for (item = array->items; item->prog && i < cnt; item++) {
> if (item->prog == bpf_cgroup_array_dummy(atype))
Retroactive nit for bpf_cgroup_array_dummy: The name is pretty confusing, something
that explain that this is really a tombstone value would be nicer.
Especially since we're repurposing the cfi_stubs for per-attach type
tombstones.
> continue;
> - id = item->prog->aux->id;
> +
> + if (cgroup_bpf_is_struct_ops_atype(atype))
> + id = bpf_struct_ops_id(item->kdata);
> + else
> + id = item->prog->aux->id;
> +
> if (copy_to_user(prog_ids + i, &id, sizeof(id)))
> return -EFAULT;
> i++;
> @@ -417,7 +474,7 @@ static void cgroup_bpf_release(struct work_struct *work)
> old_array = rcu_dereference_protected(
> cgrp->bpf.effective[atype],
> lockdep_is_held(&cgroup_mutex));
> - bpf_cgroup_array_free(old_array);
> + bpf_cgroup_array_free(old_array, atype);
> }
>
> list_for_each_entry_safe(storage, stmp, storages, list_cg) {
> @@ -461,17 +518,26 @@ static struct bpf_prog *prog_list_prog(struct bpf_prog_list *pl)
>
> static void prog_list_init_item(struct bpf_prog_list *pl, struct bpf_prog_array_item *item)
> {
> - item->prog = prog_list_prog(pl);
> - bpf_cgroup_storages_assign(item->cgroup_storage, pl->storage);
> + if (pl->link && pl->link->map) {
> + item->kdata = bpf_struct_ops_map_kdata(pl->link->map);
> + } else {
> + item->prog = prog_list_prog(pl);
> + bpf_cgroup_storages_assign(item->cgroup_storage, pl->storage);
> + }
Same nit as above, an if-return avoid reformatting existing code.
> }
>
> static void prog_list_replace_item(struct bpf_prog_list *pl, struct bpf_prog_array_item *item)
> {
> - WRITE_ONCE(item->prog, pl->link->link.prog);
> + if (pl->link && pl->link->map)
Can we somehow encapsulate the pl->link && pl->link->map to give it a
more descriptive name? It's mentioned in previous commit descriptions
what it is for, but as it stands it takes some thought to realize it's
checking if this is a struct_ops item or a regular prog.
> + WRITE_ONCE(item->kdata, bpf_struct_ops_map_kdata(pl->link->map));
> + else
> + WRITE_ONCE(item->prog, pl->link->link.prog);
> }
>
> static u32 prog_list_id(struct bpf_prog_list *pl)
> {
> + if (pl->link && pl->link->map)
Ditto.
> + return pl->link->map->id;
> return prog_list_prog(pl)->aux->id;
> }
>
> @@ -591,7 +657,7 @@ static void activate_effective_progs(struct cgroup *cgrp,
> /* free prog array after grace period, since __cgroup_bpf_run_*()
> * might be still walking the array
> */
> - bpf_cgroup_array_free(old_array);
> + bpf_cgroup_array_free(old_array, atype);
> }
>
> /**
> @@ -631,7 +697,7 @@ static int cgroup_bpf_inherit(struct cgroup *cgrp)
> return 0;
> cleanup:
> for (i = 0; i < NR; i++)
> - bpf_cgroup_array_free(arrays[i]);
> + bpf_cgroup_array_free(arrays[i], i);
>
> for (p = cgroup_parent(cgrp); p; p = cgroup_parent(p))
> cgroup_bpf_put(p);
> @@ -686,7 +752,7 @@ static int update_effective_progs(struct cgroup *cgrp,
>
> if (percpu_ref_is_zero(&desc->bpf.refcnt)) {
> if (unlikely(desc->bpf.inactive)) {
> - bpf_cgroup_array_free(desc->bpf.inactive);
> + bpf_cgroup_array_free(desc->bpf.inactive, atype);
> desc->bpf.inactive = NULL;
> }
> continue;
> @@ -705,7 +771,7 @@ static int update_effective_progs(struct cgroup *cgrp,
> css_for_each_descendant_pre(css, &cgrp->self) {
> struct cgroup *desc = container_of(css, struct cgroup, self);
>
> - bpf_cgroup_array_free(desc->bpf.inactive);
> + bpf_cgroup_array_free(desc->bpf.inactive, atype);
> desc->bpf.inactive = NULL;
> }
>
> @@ -940,7 +1006,7 @@ static int __cgroup_bpf_attach(struct cgroup *cgrp,
> if (pl) {
> old_prog = pl->prog;
> } else {
> - pl = kmalloc_obj(*pl);
> + pl = kzalloc_obj(*pl);
> if (!pl) {
> bpf_cgroup_storages_free(new_storage);
> return -ENOMEM;
> @@ -1337,7 +1403,17 @@ static int __cgroup_bpf_query(struct cgroup *cgrp, const union bpf_attr *attr,
> if (effective_query && prog_attach_flags)
> return -EINVAL;
>
> - if (type == BPF_LSM_CGROUP) {
> + if (type == BPF_STRUCT_OPS) {
> + u32 type_id = attr->query.type_id;
> +
> + atype = find_atype_by_struct_ops_id(type_id);
> + if (atype == CGROUP_BPF_ATTACH_TYPE_INVALID)
> + return -ENOENT;
> + from_atype = to_atype = atype;
> + flags = 0;
> + if (!cgroup_bpf_enabled(atype))
> + goto skip_count;
> + } else if (type == BPF_LSM_CGROUP) {
> if (!effective_query && attr->query.prog_cnt &&
> prog_ids && !prog_attach_flags)
> return -EINVAL;
> @@ -1363,6 +1439,7 @@ static int __cgroup_bpf_query(struct cgroup *cgrp, const union bpf_attr *attr,
> }
> }
>
> +skip_count:
> /* always output uattr->query.attach_flags as 0 during effective query */
> flags = effective_query ? 0 : flags;
> if (copy_to_user(&uattr->query.attach_flags, &flags, sizeof(flags)))
> @@ -2820,6 +2897,270 @@ const struct bpf_verifier_ops cg_sockopt_verifier_ops = {
> const struct bpf_prog_ops cg_sockopt_prog_ops = {
> };
>
> +static int __cgroup_struct_ops_link_detach(struct bpf_link *link, bool wake_poll)
> +{
> + struct bpf_cgroup_link *cg_link = container_of(link, struct bpf_cgroup_link, link);
> + enum cgroup_bpf_attach_type atype;
> + struct bpf_prog_list *pl;
> + struct bpf_map *map;
> + struct cgroup *cgrp;
> +
> + cgroup_lock();
> +
> + cgrp = cg_link->cgroup;
> + if (!cgrp) {
> + cgroup_unlock();
> + return 0;
> + }
> +
> + map = cg_link->map;
> + atype = bpf_struct_ops_map_cgroup_atype(map);
> +
> + hlist_for_each_entry(pl, &cgrp->bpf.progs[atype], node) {
> + if (pl->link == cg_link)
> + break;
> + }
What happens if the link isn't found here? Update deals this with
-ENOENT. Even though it's only possible if there's a bug I think
we should at least have a warning.
> +
> + /* mark deleted so compute_effective_progs() skips it */
> + pl->link = NULL;
> + if (update_effective_progs(cgrp, atype)) {
> + pl->link = cg_link;
> + purge_effective_progs(cgrp, pl, atype);
> + }
> +
> + hlist_del(&pl->node);
> + cgroup_struct_ops_link_detach_wake(cg_link, wake_poll);
> + cgrp->bpf.revisions[atype]++;
> +
> + kfree(pl);
> + static_branch_dec(&cgroup_bpf_enabled_key[atype]);
> +
> + cgroup_unlock();
> +
> + return 0;
> +}
> +
> +static int cgroup_struct_ops_link_detach(struct bpf_link *link)
> +{
> + return __cgroup_struct_ops_link_detach(link, true);
> +}
> +
> +static void cgroup_struct_ops_link_dealloc(struct bpf_link *link)
> +{
> + struct bpf_cgroup_link *cg_link = container_of(link, struct bpf_cgroup_link, link);
> +
> + __cgroup_struct_ops_link_detach(link, false);
> + kfree(cg_link);
> +}
> +
> +static void cgroup_struct_ops_link_show_fdinfo(const struct bpf_link *link, struct seq_file *seq)
> +{
> + struct bpf_cgroup_link *cg_link =
> + container_of(link, struct bpf_cgroup_link, link);
> +
> + cgroup_lock();
> + if (!cg_link->cgroup) {
> + cgroup_unlock();
> + return;
> + }
> +
> + seq_printf(seq, "map_id:\t%u\n", cg_link->map->id);
> + seq_printf(seq, "cgroup_id:\t%llu\n", cgroup_id(cg_link->cgroup));
> + cgroup_unlock();
> +}
> +
> +static int cgroup_struct_ops_link_fill_link_info(const struct bpf_link *link,
> + struct bpf_link_info *info)
> +{
> + struct bpf_cgroup_link *cg_link = container_of(link, struct bpf_cgroup_link, link);
> +
> + cgroup_lock();
> + if (!cg_link->cgroup) {
> + cgroup_unlock();
> + return 0;
> + }
> +
> + info->struct_ops.map_id = cg_link->map->id;
> + info->struct_ops.cgroup_id = cgroup_id(cg_link->cgroup);
> + cgroup_unlock();
> + return 0;
> +}
> +
> +static int cgroup_struct_ops_link_update(struct bpf_link *link, struct bpf_map *new_map,
> + struct bpf_map *expected_old_map)
> +{
> + struct bpf_cgroup_link *cg_link = container_of(link, struct bpf_cgroup_link, link);
> + enum cgroup_bpf_attach_type atype;
> + struct bpf_prog_list *pl;
> + struct bpf_map *old_map;
> + struct cgroup *cgrp;
> + bool found = false;
> + int err;
> +
> + if (!bpf_struct_ops_valid_to_reg(new_map))
> + return -EINVAL;
> +
> + cgroup_lock();
> +
> + cgrp = cg_link->cgroup;
> + if (!cgrp) {
> + err = -ENOLINK;
> + goto out;
> + }
> +
> + old_map = cg_link->map;
> + err = bpf_struct_ops_link_update_check(new_map, old_map, expected_old_map);
> + if (err)
> + goto out;
> +
> + atype = bpf_struct_ops_map_cgroup_atype(new_map);
> +
> + hlist_for_each_entry(pl, &cgrp->bpf.progs[atype], node) {
> + if (pl->link == cg_link) {
> + found = true;
> + break;
> + }
> + }
> + if (!found) {
> + err = -ENOENT;
> + goto out;
> + }
> +
> + bpf_map_inc(new_map);
> + WRITE_ONCE(cg_link->map, new_map);
> + replace_effective_prog(cgrp, atype, pl);
> + bpf_map_put(old_map);
> + cgrp->bpf.revisions[atype]++;
> +
> +out:
> + cgroup_unlock();
> + return err;
> +}
> +
> +static __poll_t cgroup_struct_ops_link_poll(struct file *file, struct poll_table_struct *pts)
> +{
> + struct bpf_cgroup_link *link = file->private_data;
> +
> + poll_wait(file, &link->wait_hup, pts);
> +
> + return READ_ONCE(link->map) ? 0 : EPOLLHUP;
> +}
> +
> +static const struct bpf_link_ops cgroup_struct_ops_link_ops = {
> + .dealloc = cgroup_struct_ops_link_dealloc,
> + .detach = cgroup_struct_ops_link_detach,
> + .show_fdinfo = cgroup_struct_ops_link_show_fdinfo,
> + .fill_link_info = cgroup_struct_ops_link_fill_link_info,
> + .update_map = cgroup_struct_ops_link_update,
> + .poll = cgroup_struct_ops_link_poll,
> +};
> +
> +int cgroup_bpf_struct_ops_attach(struct bpf_map *map, const union bpf_attr *attr)
> +{
> + u32 flags = attr->link_create.flags;
> + u32 pl_flags = (flags & BPF_F_PREORDER) | BPF_F_ALLOW_MULTI;
> + enum cgroup_bpf_attach_type atype;
> + struct bpf_link_primer link_primer;
> + struct bpf_cgroup_link *link;
> + struct bpf_prog_list *pl = NULL;
> + struct hlist_head *progs;
> + struct cgroup *cgrp;
> + int err;
> +
> + if (flags & ~BPF_F_LINK_ATTACH_MASK)
> + return -EINVAL;
> +
> + /*
> + * Attaching struct_ops to cgroup is through link only. All relative
> + * position must be corresponding to a link id or fd.
> + */
> + if (attr->link_create.cgroup.relative_fd && !(flags & BPF_F_LINK))
> + return -EINVAL;
> +
> + link = kzalloc_obj(*link, GFP_USER);
> + if (!link)
> + return -ENOMEM;
> +
> + bpf_link_init(&link->link, BPF_LINK_TYPE_STRUCT_OPS,
> + &cgroup_struct_ops_link_ops, NULL,
> + attr->link_create.attach_type);
> +
> + err = bpf_link_prime(&link->link, &link_primer);
> + if (err) {
> + kfree(link);
> + return err;
> + }
> +
> + cgrp = cgroup_get_from_fd(attr->link_create.target_fd);
> + if (IS_ERR(cgrp)) {
> + err = PTR_ERR(cgrp);
> + goto cleanup;
> + }
> +
> + bpf_map_inc(map);
> + link->map = map;
> + link->cgroup = cgrp;
> + init_waitqueue_head(&link->wait_hup);
> +
> + atype = bpf_struct_ops_map_cgroup_atype(map);
> + progs = &cgrp->bpf.progs[atype];
> +
> + cgroup_lock();
> +
> + if (attr->link_create.cgroup.expected_revision &&
> + attr->link_create.cgroup.expected_revision != cgrp->bpf.revisions[atype]) {
> + err = -ESTALE;
> + goto unlock;
> + }
> +
> + if (prog_list_length(progs, NULL) >= BPF_CGROUP_MAX_PROGS) {
> + err = -E2BIG;
> + goto unlock;
> + }
> +
> + pl = kzalloc_obj(*pl);
> + if (!pl) {
> + err = -ENOMEM;
> + goto unlock;
> + }
> +
> + pl->link = link;
> + pl->flags = pl_flags;
> + cgrp->bpf.flags[atype] = BPF_F_ALLOW_MULTI;
> +
> + err = insert_pl_to_hlist(pl, progs, NULL, link,
> + flags | BPF_F_ALLOW_MULTI, attr->link_create.cgroup.relative_fd);
> + if (err)
> + goto unlock;
> +
> + err = update_effective_progs(cgrp, atype);
> + if (err) {
> + hlist_del(&pl->node);
> + goto unlock;
> + }
> +
> + cgrp->bpf.revisions[atype]++;
> + static_branch_inc(&cgroup_bpf_enabled_key[atype]);
> +
> + cgroup_unlock();
> +
> + return bpf_link_settle(&link_primer);
> +
> +unlock:
> + cgroup_unlock();
> +
> +cleanup:
> + kfree(pl);
> + if (link->cgroup) {
> + cgroup_put(link->cgroup);
> + link->cgroup = NULL;
> + bpf_map_put(link->map);
> + link->map = NULL;
> + }
> + bpf_link_cleanup(&link_primer);
> + return err;
> +}
> +
> /* Common helpers for cgroup hooks. */
> const struct bpf_func_proto *
> cgroup_common_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
> diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
> index b07acf37ad1d..b439c0b0eadd 100644
> --- a/kernel/bpf/syscall.c
> +++ b/kernel/bpf/syscall.c
> @@ -4813,6 +4813,7 @@ static int bpf_prog_query(const union bpf_attr *attr,
> case BPF_CGROUP_GETSOCKOPT:
> case BPF_CGROUP_SETSOCKOPT:
> case BPF_LSM_CGROUP:
> + case BPF_STRUCT_OPS:
> return cgroup_bpf_prog_query(attr, uattr, uattr_size);
> case BPF_LIRC_MODE2:
> return lirc_prog_query(attr, uattr);
> diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h
> index c91b5a4bda03..c982801ec7db 100644
> --- a/tools/include/uapi/linux/bpf.h
> +++ b/tools/include/uapi/linux/bpf.h
> @@ -1756,7 +1756,7 @@ union bpf_attr {
> __u32 prog_cnt;
> __u32 count;
> };
> - __u32 :32;
> + __u32 type_id;
> /* output: per-program attach_flags.
> * not allowed to be set during effective query.
> */
> @@ -6818,6 +6818,8 @@ struct bpf_link_info {
> } xdp;
> struct {
> __u32 map_id;
> + __u32 :32;
> + __u64 cgroup_id;
> } struct_ops;
> struct {
> __u32 pf;
^ permalink raw reply
* [PATCH net-next V6 0/4] devlink: Add boot-time eswitch mode defaults
From: Mark Bloch @ 2026-07-14 6:17 UTC (permalink / raw)
To: Jiri Pirko, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman
Cc: Saeed Mahameed, Leon Romanovsky, Tariq Toukan, Andrew Lunn,
Jonathan Corbet, Shuah Khan, netdev, linux-rdma, linux-doc,
Mark Bloch
This series adds a devlink_eswitch_mode= kernel command line parameter
for setting a default devlink eswitch mode during boot.
Following the discussion with Jakub[1] and the feedback on the RFC
postings, this version keeps the scope limited to a boot-time devlink
eswitch mode default only.
The option selects either all devlink handles or an explicit
comma-separated handle list:
devlink_eswitch_mode=*=switchdev
devlink_eswitch_mode=pci/0000:08:00.0,pci/0000:09:00.1=switchdev_inactive
The supported modes are legacy, switchdev and switchdev_inactive. The
selected mode is applied through the existing eswitch_mode_set() devlink
operation, the same operation used by the devlink eswitch mode command.
Registration may happen while a driver holds the devlink lock and
continues device initialization. Devlink core marks the default as
pending and applies it from devl_unlock() once the instance is
registered, before releasing the lock. This prevents userspace from
racing with the boot default.
After a successful reload that performed DRIVER_REINIT, devlink core
already holds the devlink instance lock and the driver completed
reload_up(), so the default is applied directly from the reload path.
Patch 1 clears the mlx5 FW reset-in-progress bit before reload.
Patch 2 factors the common eswitch mode set validation into a helper.
Patch 3 adds the devlink_eswitch_mode= parser and documentation.
Patch 4 applies parsed defaults from devlink core.
Changelog:
v5 -> v6:
- Dropped regular work and applied a pending default directly from
devl_unlock() while the devlink instance lock is still held.
- Dropped the driver API and mlx5-specific default application patches.
v4 -> v5:
- Moved the default eswitch mode code into a separate file, per Jiri's
comment.
- Dropped the delayed workqueue and switched to regular work triggered
via devl_unlock(), per Jiri's comment.
- Renamed some functions to better align with devlink code.
v3 -> v4:
- Rework registration time apply to use per devlink work queued from
devl_unlock(), instead of calling eswitch_mode_set() directly from
devl_register().
- Apply the default directly after successful DRIVER_REINIT devlink reload,
where the devlink lock is already held and reload_up() has completed.
- Add devl_apply_default_esw_mode() for drivers that know their exact ready
point.
- Drop the driver registration-ordering preparation patches that are no
longer needed with the async registration apply path.
v2 -> v3:
- Change the devlink_eswitch_mode= API syntax to use <selector>=<mode>
instead of [<selector>]:<mode>, following a comment from Randy Dunlap.
v1 -> v2:
- Move default eswitch mode application into devlink core. The default is
now applied during devlink registration and after a successful devlink
reload that performed DRIVER_REINIT.
- Remove the exported devl_apply_default_esw_mode() driver API and the mlx5
driver-side call to it.
- Skip devlink health recovery notifications while the devlink instance is
not registered, so drivers can move registration later without early
health work hitting registration assertions.
- Move mlx5 devlink registration after device initialization, including the
lightweight init path, so the core can apply the default through the
normal registration flow.
- Move the matching netdevsim and mlx5 unregister paths before object
teardown, so unregister notifications come from devl_unregister() and the
later object teardown paths run while the devlink instance is no longer
registered.
- Add registration-ordering preparation patches for netdevsim and octeontx2
AF/PF, so their eswitch state is ready before registration-time defaults
may call eswitch_mode_set().
[1] lore.kernel.org/r/20260502184153.4fd8d06f@kernel.org/
RFC v1: lore.kernel.org/r/20260506123739.1959770-1-mbloch@nvidia.com/
RFC v2: lore.kernel.org/r/20260510185424.2041415-1-mbloch@nvidia.com/
v1: lore.kernel.org/r/20260521072434.362624-1-tariqt@nvidia.com/
v2: lore.kernel.org/all/20260603193259.3412464-1-mbloch@nvidia.com/
v3: lore.kernel.org/all/20260605181030.3486619-1-mbloch@nvidia.com/
v4: lore.kernel.org/all/20260629182102.245150-1-mbloch@nvidia.com/
v5: lore.kernel.org/all/20260707174527.425134-1-mbloch@nvidia.com/
Mark Bloch (4):
net/mlx5: Clear FW reset-in-progress bit before reload
devlink: Factor out eswitch mode setting
devlink: Parse eswitch mode boot defaults
devlink: Apply eswitch mode boot defaults
.../admin-guide/kernel-parameters.txt | 25 ++
.../networking/devlink/devlink-defaults.rst | 78 +++++
Documentation/networking/devlink/index.rst | 1 +
.../ethernet/mellanox/mlx5/core/fw_reset.c | 28 +-
net/devlink/Makefile | 2 +-
net/devlink/core.c | 10 +
net/devlink/default.c | 303 ++++++++++++++++++
net/devlink/dev.c | 33 +-
net/devlink/devl_internal.h | 10 +
9 files changed, 471 insertions(+), 19 deletions(-)
create mode 100644 Documentation/networking/devlink/devlink-defaults.rst
create mode 100644 net/devlink/default.c
base-commit: f6f3b36c15ed44de1fbb44e645e4fae8c4a4453e
--
2.43.0
^ permalink raw reply
* [PATCH net-next V6 4/4] devlink: Apply eswitch mode boot defaults
From: Mark Bloch @ 2026-07-14 6:17 UTC (permalink / raw)
To: Jiri Pirko, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman
Cc: Saeed Mahameed, Leon Romanovsky, Tariq Toukan, Andrew Lunn,
Jonathan Corbet, Shuah Khan, netdev, linux-rdma, linux-doc,
Mark Bloch
In-Reply-To: <20260714061731.531849-1-mbloch@nvidia.com>
Apply parsed devlink_eswitch_mode= defaults after devlink registration
and after successful reload.
Mark the default mode as pending when a devlink instance is allocated.
Before devl_unlock() releases the instance lock, apply a pending default
when the instance is registered.
Clear the pending state before calling into the driver so the boot
default remains a one-shot operation even if the mode change fails.
For successful reloads that performed DRIVER_REINIT, devlink_reload()
already holds the devlink instance lock and the driver has completed
reload_up(). Clear the pending state and apply the default directly from
the reload path.
Treat an explicit user eswitch mode request as consuming the pending
default mode.
Signed-off-by: Mark Bloch <mbloch@nvidia.com>
---
net/devlink/core.c | 3 ++
net/devlink/default.c | 70 +++++++++++++++++++++++++++++++++++--
net/devlink/dev.c | 6 ++++
net/devlink/devl_internal.h | 5 +++
4 files changed, 82 insertions(+), 2 deletions(-)
diff --git a/net/devlink/core.c b/net/devlink/core.c
index fc14ee5d9dcf..cea4afc27dd9 100644
--- a/net/devlink/core.c
+++ b/net/devlink/core.c
@@ -317,6 +317,7 @@ EXPORT_SYMBOL_GPL(devl_trylock);
void devl_unlock(struct devlink *devlink)
{
+ devlink_default_esw_mode_apply_pending(devlink);
mutex_unlock(&devlink->lock);
}
EXPORT_SYMBOL_GPL(devl_unlock);
@@ -429,6 +430,7 @@ void devl_unregister(struct devlink *devlink)
ASSERT_DEVLINK_REGISTERED(devlink);
devl_assert_locked(devlink);
+ devlink_default_esw_mode_apply_pending_clear(devlink);
devlink_notify_unregister(devlink);
xa_clear_mark(&devlinks, devlink->index, DEVLINK_REGISTERED);
devlink_rel_put(devlink);
@@ -490,6 +492,7 @@ struct devlink *__devlink_alloc(const struct devlink_ops *ops, size_t priv_size,
INIT_LIST_HEAD(&devlink->trap_group_list);
INIT_LIST_HEAD(&devlink->trap_policer_list);
INIT_RCU_WORK(&devlink->rwork, devlink_release);
+ devlink_default_esw_mode_instance_init(devlink);
lockdep_register_key(&devlink->lock_key);
mutex_init(&devlink->lock);
lockdep_set_class(&devlink->lock, &devlink->lock_key);
diff --git a/net/devlink/default.c b/net/devlink/default.c
index 8434af83ea69..77cc356dfac9 100644
--- a/net/devlink/default.c
+++ b/net/devlink/default.c
@@ -10,6 +10,7 @@
static char *devlink_default_esw_mode_param;
static bool devlink_default_esw_mode_match_all;
+static bool devlink_default_esw_mode_enabled;
static enum devlink_eswitch_mode devlink_default_esw_mode;
static LIST_HEAD(devlink_default_esw_mode_nodes);
@@ -154,6 +155,7 @@ static void __init devlink_default_esw_mode_nodes_clear(void)
}
devlink_default_esw_mode_match_all = false;
+ devlink_default_esw_mode_enabled = false;
}
static int __init devlink_default_esw_mode_parse(char *str)
@@ -180,14 +182,78 @@ static int __init devlink_default_esw_mode_parse(char *str)
return err;
err = devlink_default_esw_mode_handles_parse(handles);
- if (err)
+ if (err) {
devlink_default_esw_mode_nodes_clear();
- else
+ } else {
devlink_default_esw_mode = esw_mode;
+ devlink_default_esw_mode_enabled = true;
+ }
return err;
}
+static bool devlink_default_esw_mode_match(struct devlink *devlink)
+{
+ const char *bus_name = devlink_bus_name(devlink);
+ const char *dev_name = devlink_dev_name(devlink);
+ struct devlink_default_esw_mode_node *node;
+
+ if (devlink_default_esw_mode_match_all)
+ return true;
+
+ node = devlink_default_esw_mode_node_find(bus_name, dev_name);
+ return !!node;
+}
+
+void devlink_default_esw_mode_apply_locked(struct devlink *devlink)
+{
+ const struct devlink_ops *ops = devlink->ops;
+ int err;
+
+ devl_assert_locked(devlink);
+
+ if (!devlink_default_esw_mode_match(devlink))
+ return;
+
+ if (!ops->eswitch_mode_set) {
+ if (!devlink_default_esw_mode_match_all)
+ devl_warn(devlink,
+ "devlink_eswitch_mode= selected this device but eswitch mode setting is not supported\n");
+ return;
+ }
+
+ err = devlink_eswitch_mode_set(devlink, devlink_default_esw_mode, NULL);
+ if (err)
+ devl_warn(devlink,
+ "Couldn't apply default eswitch mode, err %d\n",
+ err);
+}
+
+void devlink_default_esw_mode_apply_pending(struct devlink *devlink)
+{
+ devl_assert_locked(devlink);
+
+ if (!devlink->default_esw_mode_apply_pending ||
+ !__devl_is_registered(devlink))
+ return;
+
+ devlink->default_esw_mode_apply_pending = false;
+ devlink_default_esw_mode_apply_locked(devlink);
+}
+
+void devlink_default_esw_mode_instance_init(struct devlink *devlink)
+{
+ devlink->default_esw_mode_apply_pending =
+ devlink_default_esw_mode_enabled;
+}
+
+void devlink_default_esw_mode_apply_pending_clear(struct devlink *devlink)
+{
+ devl_assert_locked(devlink);
+
+ devlink->default_esw_mode_apply_pending = false;
+}
+
static int __init devlink_default_esw_mode_setup(char *str)
{
devlink_default_esw_mode_param = str;
diff --git a/net/devlink/dev.c b/net/devlink/dev.c
index 119ef105d0a7..611bb6bfd492 100644
--- a/net/devlink/dev.c
+++ b/net/devlink/dev.c
@@ -478,6 +478,11 @@ int devlink_reload(struct devlink *devlink, struct net *dest_net,
return err;
WARN_ON(!(*actions_performed & BIT(action)));
+ if (*actions_performed & BIT(DEVLINK_RELOAD_ACTION_DRIVER_REINIT)) {
+ devlink_default_esw_mode_apply_pending_clear(devlink);
+ devlink_default_esw_mode_apply_locked(devlink);
+ }
+
/* Catch driver on updating the remote action within devlink reload */
WARN_ON(memcmp(remote_reload_stats, devlink->stats.remote_reload_stats,
sizeof(remote_reload_stats)));
@@ -731,6 +736,7 @@ int devlink_nl_eswitch_set_doit(struct sk_buff *skb, struct genl_info *info)
u16 mode;
if (info->attrs[DEVLINK_ATTR_ESWITCH_MODE]) {
+ devlink_default_esw_mode_apply_pending_clear(devlink);
mode = nla_get_u16(info->attrs[DEVLINK_ATTR_ESWITCH_MODE]);
err = devlink_eswitch_mode_set(devlink, mode, info->extack);
if (err)
diff --git a/net/devlink/devl_internal.h b/net/devlink/devl_internal.h
index fe9ad58515d4..97f53394b1c0 100644
--- a/net/devlink/devl_internal.h
+++ b/net/devlink/devl_internal.h
@@ -58,6 +58,7 @@ struct devlink {
struct mutex lock;
struct lock_class_key lock_key;
u8 reload_failed:1;
+ u8 default_esw_mode_apply_pending:1;
refcount_t refcount;
struct rcu_work rwork;
struct devlink_rel *rel;
@@ -73,6 +74,10 @@ struct devlink *__devlink_alloc(const struct devlink_ops *ops, size_t priv_size,
const struct device_driver *dev_driver);
int devlink_default_esw_mode_init(void);
void devlink_default_esw_mode_cleanup(void);
+void devlink_default_esw_mode_instance_init(struct devlink *devlink);
+void devlink_default_esw_mode_apply_locked(struct devlink *devlink);
+void devlink_default_esw_mode_apply_pending(struct devlink *devlink);
+void devlink_default_esw_mode_apply_pending_clear(struct devlink *devlink);
#define devl_warn(devlink, format, args...) \
do { \
--
2.43.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox