* Re: [PATCH net V2 2/2] net: xilinx: axienet: Fix BQL accounting for multi-BD TX packets
From: Sean Anderson @ 2026-03-30 18:49 UTC (permalink / raw)
To: Suraj Gupta, Radhey Shyam Pandey, Andrew Lunn, David S . Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni
Cc: Michal Simek, Daniel Borkmann, Ariane Keller, netdev,
linux-arm-kernel, linux-kernel
In-Reply-To: <20260327073238.134948-3-suraj.gupta2@amd.com>
On 3/27/26 03:32, Suraj Gupta wrote:
> When a TX packet spans multiple buffer descriptors (scatter-gather),
> axienet_free_tx_chain sums the per-BD actual length from descriptor
> status into a caller-provided accumulator. That sum is reset on each
> NAPI poll. If the BDs for a single packet complete across different
> polls, the earlier bytes are lost and never credited to BQL. This
> causes BQL to think bytes are permanently in-flight, eventually
> stalling the TX queue.
>
> The SKB pointer is stored only on the last BD of a packet. When that
> BD completes, use skb->len for the byte count instead of summing
> per-BD status lengths. This matches netdev_sent_queue(), which debits
> skb->len, and naturally survives across polls because no partial
> packet contributes to the accumulator.
>
> Fixes: c900e49d58eb ("net: xilinx: axienet: Implement BQL")
> Signed-off-by: Suraj Gupta <suraj.gupta2@amd.com>
> ---
> drivers/net/ethernet/xilinx/xilinx_axienet_main.c | 9 ++++-----
> 1 file changed, 4 insertions(+), 5 deletions(-)
>
> diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c
> index b06e4c37ff61..263c4b67fd5a 100644
> --- a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c
> +++ b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c
> @@ -770,8 +770,8 @@ static int axienet_device_reset(struct net_device *ndev)
> * @first_bd: Index of first descriptor to clean up
> * @nr_bds: Max number of descriptors to clean up
> * @force: Whether to clean descriptors even if not complete
> - * @sizep: Pointer to a u32 filled with the total sum of all bytes
> - * in all cleaned-up descriptors. Ignored if NULL.
> + * @sizep: Pointer to a u32 accumulating the total byte count of
> + * completed packets (using skb->len). Ignored if NULL.
> * @budget: NAPI budget (use 0 when not called from NAPI poll)
> *
> * Would either be called after a successful transmit operation, or after
> @@ -805,6 +805,8 @@ static int axienet_free_tx_chain(struct axienet_local *lp, u32 first_bd,
> DMA_TO_DEVICE);
>
> if (cur_p->skb && (status & XAXIDMA_BD_STS_COMPLETE_MASK)) {
> + if (sizep)
> + *sizep += cur_p->skb->len;
> napi_consume_skb(cur_p->skb, budget);
> packets++;
> }
> @@ -818,9 +820,6 @@ static int axienet_free_tx_chain(struct axienet_local *lp, u32 first_bd,
> wmb();
> cur_p->cntrl = 0;
> cur_p->status = 0;
> -
> - if (sizep)
> - *sizep += status & XAXIDMA_BD_STS_ACTUAL_LEN_MASK;
> }
>
> if (!force) {
Reviewed-by: Sean Anderson <sean.anderson@linux.dev>
Although FWIW this may result in slightly-different statistics. Not sure
if we care since this will only affect packets that could not be sent
for whatever reason (collisions, loss of carrier, etc.)
^ permalink raw reply
* [PATCH net 1/2] net: macb: fix clk handling on PCI glue driver removal
From: Fedor Pchelkin @ 2026-03-30 18:45 UTC (permalink / raw)
To: Nicolas Ferre, Claudiu Beznea, Théo Lebrun
Cc: Fedor Pchelkin, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Tong Zhang, Bartosz Folta, netdev,
linux-kernel, lvc-project
platform_device_unregister() may still want to use the registered clks
during runtime resume callback.
Note that there is a commit d82d5303c4c5 ("net: macb: fix use after free
on rmmod") that addressed the similar problem of clk vs platform device
unregistration but just moved the bug to another place.
Save the pointers to clks into local variables for reuse after platform
device is unregistered.
BUG: KASAN: use-after-free in clk_prepare+0x5a/0x60
Read of size 8 at addr ffff888104f85e00 by task modprobe/597
CPU: 2 PID: 597 Comm: modprobe Not tainted 6.1.164+ #114
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS rel-1.16.1-0-g3208b098f51a-prebuilt.qemu.org 04/01/2014
Call Trace:
<TASK>
dump_stack_lvl+0x8d/0xba
print_report+0x17f/0x496
kasan_report+0xd9/0x180
clk_prepare+0x5a/0x60
macb_runtime_resume+0x13d/0x410 [macb]
pm_generic_runtime_resume+0x97/0xd0
__rpm_callback+0xc8/0x4d0
rpm_callback+0xf6/0x230
rpm_resume+0xeeb/0x1a70
__pm_runtime_resume+0xb4/0x170
bus_remove_device+0x2e3/0x4b0
device_del+0x5b3/0xdc0
platform_device_del+0x4e/0x280
platform_device_unregister+0x11/0x50
pci_device_remove+0xae/0x210
device_remove+0xcb/0x180
device_release_driver_internal+0x529/0x770
driver_detach+0xd4/0x1a0
bus_remove_driver+0x135/0x260
driver_unregister+0x72/0xb0
pci_unregister_driver+0x26/0x220
__do_sys_delete_module+0x32e/0x550
do_syscall_64+0x35/0x80
entry_SYSCALL_64_after_hwframe+0x6e/0xd8
</TASK>
Allocated by task 519:
kasan_save_stack+0x2c/0x50
kasan_set_track+0x21/0x30
__kasan_kmalloc+0x8e/0x90
__clk_register+0x458/0x2890
clk_hw_register+0x1a/0x60
__clk_hw_register_fixed_rate+0x255/0x410
clk_register_fixed_rate+0x3c/0xa0
macb_probe+0x1d8/0x42e [macb_pci]
local_pci_probe+0xd7/0x190
pci_device_probe+0x252/0x600
really_probe+0x255/0x7f0
__driver_probe_device+0x1ee/0x330
driver_probe_device+0x4c/0x1f0
__driver_attach+0x1df/0x4e0
bus_for_each_dev+0x15d/0x1f0
bus_add_driver+0x486/0x5e0
driver_register+0x23a/0x3d0
do_one_initcall+0xfd/0x4d0
do_init_module+0x18b/0x5a0
load_module+0x5663/0x7950
__do_sys_finit_module+0x101/0x180
do_syscall_64+0x35/0x80
entry_SYSCALL_64_after_hwframe+0x6e/0xd8
Freed by task 597:
kasan_save_stack+0x2c/0x50
kasan_set_track+0x21/0x30
kasan_save_free_info+0x2a/0x50
__kasan_slab_free+0x106/0x180
__kmem_cache_free+0xbc/0x320
clk_unregister+0x6de/0x8d0
macb_remove+0x73/0xc0 [macb_pci]
pci_device_remove+0xae/0x210
device_remove+0xcb/0x180
device_release_driver_internal+0x529/0x770
driver_detach+0xd4/0x1a0
bus_remove_driver+0x135/0x260
driver_unregister+0x72/0xb0
pci_unregister_driver+0x26/0x220
__do_sys_delete_module+0x32e/0x550
do_syscall_64+0x35/0x80
entry_SYSCALL_64_after_hwframe+0x6e/0xd8
Found by Linux Verification Center (linuxtesting.org) with Syzkaller.
Fixes: d82d5303c4c5 ("net: macb: fix use after free on rmmod")
Signed-off-by: Fedor Pchelkin <pchelkin@ispras.ru>
---
drivers/net/ethernet/cadence/macb_pci.c | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/cadence/macb_pci.c b/drivers/net/ethernet/cadence/macb_pci.c
index fc4f5aee6ab3..0ce5b736ea43 100644
--- a/drivers/net/ethernet/cadence/macb_pci.c
+++ b/drivers/net/ethernet/cadence/macb_pci.c
@@ -109,10 +109,12 @@ static void macb_remove(struct pci_dev *pdev)
{
struct platform_device *plat_dev = pci_get_drvdata(pdev);
struct macb_platform_data *plat_data = dev_get_platdata(&plat_dev->dev);
+ struct clk *pclk = plat_data->pclk;
+ struct clk *hclk = plat_data->hclk;
- clk_unregister(plat_data->pclk);
- clk_unregister(plat_data->hclk);
platform_device_unregister(plat_dev);
+ clk_unregister(pclk);
+ clk_unregister(hclk);
}
static const struct pci_device_id dev_id_table[] = {
--
2.53.0
^ permalink raw reply related
* [PATCH net 2/2] net: macb: properly unregister fixed rate clocks
From: Fedor Pchelkin @ 2026-03-30 18:45 UTC (permalink / raw)
To: Nicolas Ferre, Claudiu Beznea, Théo Lebrun
Cc: Fedor Pchelkin, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Tong Zhang, Bartosz Folta, netdev,
linux-kernel, lvc-project
In-Reply-To: <20260330184542.626619-1-pchelkin@ispras.ru>
The additional resources allocated with clk_register_fixed_rate() need
to be released with clk_unregister_fixed_rate(), otherwise they are lost.
Found by Linux Verification Center (linuxtesting.org).
Fixes: 83a77e9ec415 ("net: macb: Added PCI wrapper for Platform Driver.")
Signed-off-by: Fedor Pchelkin <pchelkin@ispras.ru>
---
drivers/net/ethernet/cadence/macb_pci.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/drivers/net/ethernet/cadence/macb_pci.c b/drivers/net/ethernet/cadence/macb_pci.c
index 0ce5b736ea43..b79dec17e6b0 100644
--- a/drivers/net/ethernet/cadence/macb_pci.c
+++ b/drivers/net/ethernet/cadence/macb_pci.c
@@ -96,10 +96,10 @@ static int macb_probe(struct pci_dev *pdev, const struct pci_device_id *id)
return 0;
err_plat_dev_register:
- clk_unregister(plat_data.hclk);
+ clk_unregister_fixed_rate(plat_data.hclk);
err_hclk_register:
- clk_unregister(plat_data.pclk);
+ clk_unregister_fixed_rate(plat_data.pclk);
err_pclk_register:
return err;
@@ -113,8 +113,8 @@ static void macb_remove(struct pci_dev *pdev)
struct clk *hclk = plat_data->hclk;
platform_device_unregister(plat_dev);
- clk_unregister(pclk);
- clk_unregister(hclk);
+ clk_unregister_fixed_rate(pclk);
+ clk_unregister_fixed_rate(hclk);
}
static const struct pci_device_id dev_id_table[] = {
--
2.53.0
^ permalink raw reply related
* [PATCH net-next v2 4/4] net: dsa: initial support for MT7628 embedded switch
From: Joris Vaisvila @ 2026-03-30 18:40 UTC (permalink / raw)
To: netdev
Cc: horms, pabeni, kuba, edumazet, davem, olteanv, Andrew Lunn,
devicetree, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Joris Vaisvila
In-Reply-To: <20260330184017.766200-1-joey@tinyisr.com>
Add support for the MT7628 embedded switch.
The switch has 5 built-in 100Mbps user ports (ports 0-4) and one 1Gbps
port that is internally attached to the SoCs CPU MAC and serves as the
CPU port.
The switch hardware has a very limited 16 entry VLAN table. Configuring
VLANs is the only way to control switch forwarding. Currently 6 entries
are used by tag_8021q to isolate the ports. Double tag feature is
enabled to force the switch to append the VLAN tag even if the incoming
packet is already tagged, this simulates VLAN-unaware functionality and
simplifies the tagger implementation.
Signed-off-by: Joris Vaisvila <joey@tinyisr.com>
---
drivers/net/dsa/Kconfig | 8 +
drivers/net/dsa/Makefile | 1 +
drivers/net/dsa/mt7628.c | 627 +++++++++++++++++++++++++++++++++++++++
3 files changed, 636 insertions(+)
create mode 100644 drivers/net/dsa/mt7628.c
diff --git a/drivers/net/dsa/Kconfig b/drivers/net/dsa/Kconfig
index 39fb8ead16b5..defe74625cef 100644
--- a/drivers/net/dsa/Kconfig
+++ b/drivers/net/dsa/Kconfig
@@ -28,6 +28,14 @@ source "drivers/net/dsa/hirschmann/Kconfig"
source "drivers/net/dsa/lantiq/Kconfig"
+config NET_DSA_MT7628
+ tristate "MediaTek MT7628 Embedded Ethernet switch support"
+ select NET_DSA_TAG_MT7628
+ select MEDIATEK_FE_SOC_PHY
+ help
+ This enables support for the built-in Ethernet switch found
+ in the MT7628 SoC.
+
config NET_DSA_MT7530
tristate "MediaTek MT7530 and MT7531 Ethernet switch support"
select NET_DSA_TAG_MTK
diff --git a/drivers/net/dsa/Makefile b/drivers/net/dsa/Makefile
index f5a463b87ec2..8d4461f2d437 100644
--- a/drivers/net/dsa/Makefile
+++ b/drivers/net/dsa/Makefile
@@ -6,6 +6,7 @@ obj-$(CONFIG_NET_DSA_KS8995) += ks8995.o
obj-$(CONFIG_NET_DSA_MT7530) += mt7530.o
obj-$(CONFIG_NET_DSA_MT7530_MDIO) += mt7530-mdio.o
obj-$(CONFIG_NET_DSA_MT7530_MMIO) += mt7530-mmio.o
+obj-$(CONFIG_NET_DSA_MT7628) += mt7628.o
obj-$(CONFIG_NET_DSA_MV88E6060) += mv88e6060.o
obj-$(CONFIG_NET_DSA_RZN1_A5PSW) += rzn1_a5psw.o
obj-$(CONFIG_NET_DSA_SMSC_LAN9303) += lan9303-core.o
diff --git a/drivers/net/dsa/mt7628.c b/drivers/net/dsa/mt7628.c
new file mode 100644
index 000000000000..36461b39ea4a
--- /dev/null
+++ b/drivers/net/dsa/mt7628.c
@@ -0,0 +1,627 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Mediatek MT7628 Embedded Switch (ESW) DSA driver
+ * Copyright (C) 2026 Joris Vaisvila <joey@tinyisr.com>
+ *
+ * Portions derived from OpenWRT esw_rt3050 driver:
+ * Copyright (C) 2009-2015 John Crispin <blogic@openwrt.org>
+ * Copyright (C) 2009-2015 Felix Fietkau <nbd@nbd.name>
+ * Copyright (C) 2013-2015 Michael Lee <igvtee@gmail.com>
+ * Copyright (C) 2016 Vittorio Gambaletta <openwrt@vittgam.net>
+ */
+
+#include <linux/platform_device.h>
+#include <linux/etherdevice.h>
+#include <linux/netdevice.h>
+#include <linux/dsa/8021q.h>
+#include <linux/if_bridge.h>
+#include <linux/module.h>
+#include <linux/mdio.h>
+#include <linux/of.h>
+#include <linux/of_mdio.h>
+#include <linux/of_net.h>
+#include <linux/kernel.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <net/dsa.h>
+
+#define MT7628_ESW_REG_IMR 0x04
+#define MT7628_ESW_REG_FCT0 0x08
+#define MT7628_ESW_REG_PFC1 0x14
+#define MT7628_ESW_REG_PVIDC(port) (0x40 + 4 * ((port) / 2))
+#define MT7628_ESW_REG_VLANI(vlan) (0x50 + 4 * ((vlan) / 2))
+#define MT7628_ESW_REG_VMSC(vlan) (0x70 + 4 * ((vlan) / 4))
+#define MT7628_ESW_REG_VUB(vlan) (0x100 + 4 * ((vlan) / 4))
+#define MT7628_ESW_REG_SOCPC 0x8c
+#define MT7628_ESW_REG_POC0 0x90
+#define MT7628_ESW_REG_POC2 0x98
+#define MT7628_ESW_REG_SGC 0x9c
+#define MT7628_ESW_REG_PCR0 0xc0
+#define MT7628_ESW_REG_PCR1 0xc4
+#define MT7628_ESW_REG_FPA2 0xc8
+#define MT7628_ESW_REG_FCT2 0xcc
+#define MT7628_ESW_REG_SGC2 0xe4
+
+#define MT7628_ESW_FCT0_DROP_SET_TH GENMASK(7, 0)
+#define MT7628_ESW_FCT0_DROP_RLS_TH GENMASK(15, 8)
+#define MT7628_ESW_FCT0_FC_SET_TH GENMASK(23, 16)
+#define MT7628_ESW_FCT0_FC_RLS_TH GENMASK(31, 24)
+
+#define MT7628_ESW_PFC1_EN_VLAN GENMASK(22, 16)
+
+#define MT7628_ESW_PVID_S 12
+#define MT7628_ESW_PVID_M GENMASK(11, 0)
+#define MT7628_ESW_PVID_SHIFT(port) \
+ (MT7628_ESW_PVID_S * ((port) % 2))
+#define MT7628_ESW_PVID_MASK(port) \
+ (MT7628_ESW_PVID_M << MT7628_ESW_PVID_SHIFT(port))
+#define MT7628_ESW_PVID_PREP(port, pvid) \
+ (((pvid) & MT7628_ESW_PVID_M) << MT7628_ESW_PVID_SHIFT(port))
+
+#define MT7628_ESW_VID_S 12
+#define MT7628_ESW_VID_M GENMASK(11, 0)
+#define MT7628_ESW_VID_SHIFT(vlan) \
+ (MT7628_ESW_VID_S * ((vlan) % 2))
+#define MT7628_ESW_VID_MASK(vlan) \
+ (MT7628_ESW_VID_M << MT7628_ESW_VID_SHIFT(vlan))
+#define MT7628_ESW_VID_PREP(vlan, vid) \
+ (((vid) & MT7628_ESW_VID_M) << MT7628_ESW_VID_SHIFT(vlan))
+
+#define MT7628_ESW_VMSC_S 8
+#define MT7628_ESW_VMSC_M GENMASK(7, 0)
+#define MT7628_ESW_VMSC_SHIFT(vlan) \
+ (MT7628_ESW_VMSC_S * ((vlan) % 4))
+#define MT7628_ESW_VMSC_MASK(vlan) \
+ (MT7628_ESW_VMSC_M << MT7628_ESW_VMSC_SHIFT(vlan))
+#define MT7628_ESW_VMSC_PREP(vlan, vmsc) \
+ (((vmsc) & MT7628_ESW_VMSC_M) << MT7628_ESW_VMSC_SHIFT(vlan))
+
+#define MT7628_ESW_VUB_S 7
+#define MT7628_ESW_VUB_M GENMASK(6, 0)
+#define MT7628_ESW_VUB_SHIFT(vlan) \
+ (MT7628_ESW_VUB_S * ((vlan) % 4))
+#define MT7628_ESW_VUB_MASK(vlan) \
+ (MT7628_ESW_VUB_M << MT7628_ESW_VUB_SHIFT(vlan))
+#define MT7628_ESW_VUB_PREP(vlan, vub) \
+ (((vub) & MT7628_ESW_VUB_M) << MT7628_ESW_VUB_SHIFT(vlan))
+
+#define MT7628_ESW_SOCPC_CRC_PADDING BIT(25)
+#define MT7628_ESW_SOCPC_DISBC2CPU GENMASK(22, 16)
+#define MT7628_ESW_SOCPC_DISMC2CPU GENMASK(14, 8)
+#define MT7628_ESW_SOCPC_DISUN2CPU GENMASK(6, 0)
+
+#define MT7628_ESW_POC0_PORT_DISABLE GENMASK(29, 23)
+
+#define MT7628_ESW_POC2_PER_VLAN_UNTAG_EN BIT(15)
+
+#define MT7628_ESW_SGC_AGING_INTERVAL GENMASK(3, 0)
+#define MT7628_ESW_BC_STORM_PROT GENMASK(5, 4)
+#define MT7628_ESW_PKT_MAX_LEN GENMASK(7, 6)
+#define MT7628_ESW_DIS_PKT_ABORT BIT(8)
+#define MT7628_ESW_ADDRESS_HASH_ALG GENMASK(10, 9)
+#define MT7628_ESW_DISABLE_TX_BACKOFF BIT(11)
+#define MT7628_ESW_BP_JAM_CNT GENMASK(15, 12)
+#define MT7628_ESW_DISMIIPORT_WASTX GENMASK(17, 16)
+#define MT7628_ESW_BP_MODE GENMASK(19, 18)
+#define MT7628_ESW_BISH_DIS BIT(20)
+#define MT7628_ESW_BISH_TH GENMASK(22, 21)
+#define MT7628_ESW_LED_FLASH_TIME GENMASK(24, 23)
+#define MT7628_ESW_RMC_RULE GENMASK(26, 25)
+#define MT7628_ESW_IP_MULT_RULE GENMASK(28, 27)
+#define MT7628_ESW_LEN_ERR_CHK BIT(29)
+#define MT7628_ESW_BKOFF_ALG BIT(30)
+
+#define MT7628_ESW_PCR0_WT_NWAY_DATA GENMASK(31, 16)
+#define MT7628_ESW_PCR0_RD_PHY_CMD BIT(14)
+#define MT7628_ESW_PCR0_WT_PHY_CMD BIT(13)
+#define MT7628_ESW_PCR0_CPU_PHY_REG GENMASK(12, 8)
+#define MT7628_ESW_PCR0_CPU_PHY_ADDR GENMASK(4, 0)
+
+#define MT7628_ESW_PCR1_RD_DATA GENMASK(31, 16)
+#define MT7628_ESW_PCR1_RD_DONE BIT(1)
+#define MT7628_ESW_PCR1_WT_DONE BIT(0)
+
+#define MT7628_ESW_FPA2_AP_EN BIT(29)
+#define MT7628_ESW_FPA2_EXT_PHY_ADDR_BASE GENMASK(28, 24)
+#define MT7628_ESW_FPA2_FORCE_RGMII_LINK1 BIT(13)
+#define MT7628_ESW_FPA2_FORCE_RGMII_EN1 BIT(11)
+
+#define MT7628_ESW_FCT2_MUST_DROP_RLS_TH GENMASK(17, 13)
+#define MT7628_ESW_FCT2_MUST_DROP_SET_TH GENMASK(12, 8)
+#define MT7628_ESW_FCT2_MC_PER_PORT_TH GENMASK(5, 0)
+
+#define MT7628_ESW_SGC2_SPECIAL_TAG_EN BIT(23)
+#define MT7628_ESW_SGC2_TX_CPU_TPID_BIT_MAP GENMASK(22, 16)
+#define MT7628_ESW_SGC2_DOUBLE_TAG_EN GENMASK(6, 0)
+
+#define MT7628_ESW_PORTS_NOCPU GENMASK(5, 0)
+#define MT7628_ESW_PORTS_CPU BIT(6)
+#define MT7628_ESW_PORTS_ALL GENMASK(6, 0)
+
+#define MT7628_ESW_NUM_PORTS 7
+#define MT7628_NUM_VLANS 16
+
+static const struct regmap_config mt7628_esw_regmap_cfg = {
+ .name = "mt7628-esw",
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .fast_io = true,
+ .reg_format_endian = REGMAP_ENDIAN_LITTLE,
+ .val_format_endian = REGMAP_ENDIAN_LITTLE,
+};
+
+struct mt7628_vlan {
+ bool active;
+ u8 members;
+ u8 untag;
+ u16 vid;
+};
+
+struct mt7628_esw {
+ void __iomem *base;
+ struct reset_control *rst_ephy;
+ struct reset_control *rst_esw;
+ struct regmap *regmap;
+ struct dsa_switch *ds;
+ u16 tag_8021q_pvid[MT7628_ESW_NUM_PORTS];
+ struct mt7628_vlan vlans[MT7628_NUM_VLANS];
+ struct device *dev;
+};
+
+static int mt7628_mii_read(struct mii_bus *bus, int port, int regnum)
+{
+ struct mt7628_esw *esw = bus->priv;
+ int ret;
+ u32 val;
+
+ ret = regmap_read_poll_timeout(esw->regmap, MT7628_ESW_REG_PCR1, val,
+ !(val & MT7628_ESW_PCR1_RD_DONE), 10,
+ 5000);
+ if (ret)
+ goto out;
+
+ ret = regmap_write(esw->regmap, MT7628_ESW_REG_PCR0,
+ FIELD_PREP(MT7628_ESW_PCR0_CPU_PHY_REG,
+ regnum) |
+ FIELD_PREP(MT7628_ESW_PCR0_CPU_PHY_ADDR,
+ port) | MT7628_ESW_PCR0_RD_PHY_CMD);
+ if (ret)
+ goto out;
+
+ ret = regmap_read_poll_timeout(esw->regmap, MT7628_ESW_REG_PCR1, val,
+ (val & MT7628_ESW_PCR1_RD_DONE), 10,
+ 5000);
+out:
+ if (ret) {
+ dev_err(&bus->dev, "read failed. MDIO timeout?\n");
+ return ret;
+ }
+ return FIELD_GET(MT7628_ESW_PCR1_RD_DATA, val);
+}
+
+static int mt7628_mii_write(struct mii_bus *bus, int port, int regnum, u16 dat)
+{
+ struct mt7628_esw *esw = bus->priv;
+ u32 val;
+ int ret;
+
+ ret = regmap_read_poll_timeout(esw->regmap, MT7628_ESW_REG_PCR1, val,
+ !(val & MT7628_ESW_PCR1_WT_DONE), 10,
+ 5000);
+ if (ret)
+ goto out;
+
+ ret = regmap_write(esw->regmap, MT7628_ESW_REG_PCR0,
+ FIELD_PREP(MT7628_ESW_PCR0_WT_NWAY_DATA, dat) |
+ FIELD_PREP(MT7628_ESW_PCR0_CPU_PHY_REG,
+ regnum) |
+ FIELD_PREP(MT7628_ESW_PCR0_CPU_PHY_ADDR,
+ port) | MT7628_ESW_PCR0_WT_PHY_CMD);
+ if (ret)
+ goto out;
+
+ ret = regmap_read_poll_timeout(esw->regmap, MT7628_ESW_REG_PCR1, val,
+ (val & MT7628_ESW_PCR1_WT_DONE), 10,
+ 5000);
+out:
+ if (ret) {
+ dev_err(&bus->dev, "write failed. MDIO timeout?\n");
+ return ret;
+ }
+ return 0;
+}
+
+static int mt7628_setup_internal_mdio(struct dsa_switch *ds)
+{
+ struct mt7628_esw *esw = ds->priv;
+ struct device_node *mdio;
+ struct mii_bus *bus;
+ int ret = 0;
+
+ mdio = of_get_available_child_by_name(ds->dev->of_node, "mdio");
+
+ bus = devm_mdiobus_alloc(esw->dev);
+ if (!bus) {
+ ret = -ENOMEM;
+ goto out_put_node;
+ }
+
+ bus->name = "MT7628 internal MDIO bus";
+ snprintf(bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(ds->dev));
+ bus->priv = esw;
+ bus->read = mt7628_mii_read;
+ bus->write = mt7628_mii_write;
+ bus->parent = esw->dev;
+ if (!mdio) {
+ ds->user_mii_bus = bus;
+ bus->phy_mask = ~ds->phys_mii_mask;
+ }
+
+ ret = devm_of_mdiobus_register(esw->dev, bus, mdio);
+
+out_put_node:
+ of_node_put(mdio);
+ return ret;
+}
+
+static void mt7628_switch_init(struct dsa_switch *ds)
+{
+ struct mt7628_esw *esw = ds->priv;
+
+ regmap_write(esw->regmap, MT7628_ESW_REG_FCT0,
+ FIELD_PREP(MT7628_ESW_FCT0_DROP_SET_TH, 0x50) |
+ FIELD_PREP(MT7628_ESW_FCT0_DROP_RLS_TH, 0x78) |
+ FIELD_PREP(MT7628_ESW_FCT0_FC_SET_TH, 0xa0) |
+ FIELD_PREP(MT7628_ESW_FCT0_FC_RLS_TH, 0xc8));
+
+ regmap_write(esw->regmap, MT7628_ESW_REG_FCT2,
+ FIELD_PREP(MT7628_ESW_FCT2_MC_PER_PORT_TH, 0xc) |
+ FIELD_PREP(MT7628_ESW_FCT2_MUST_DROP_SET_TH, 0x10) |
+ FIELD_PREP(MT7628_ESW_FCT2_MUST_DROP_RLS_TH, 0x12));
+
+ /*
+ * general switch configuration:
+ * 300s aging interval
+ * broadcast storm prevention disabled
+ * max packet length 1536 bytes
+ * disable collision 16 packet abort and late collision abort
+ * use xor48 for address hashing
+ * disable tx backoff
+ * 10 packet back pressure jam
+ * disable was_transmit
+ * jam until BP condition released
+ * 30ms LED flash
+ * rmc tb fault to all ports
+ * unmatched IGMP as broadcast
+ */
+ regmap_write(esw->regmap, MT7628_ESW_REG_SGC,
+ FIELD_PREP(MT7628_ESW_SGC_AGING_INTERVAL, 1) |
+ FIELD_PREP(MT7628_ESW_BC_STORM_PROT, 0) |
+ FIELD_PREP(MT7628_ESW_PKT_MAX_LEN, 0) |
+ MT7628_ESW_DIS_PKT_ABORT |
+ FIELD_PREP(MT7628_ESW_ADDRESS_HASH_ALG, 1) |
+ MT7628_ESW_DISABLE_TX_BACKOFF |
+ FIELD_PREP(MT7628_ESW_BP_JAM_CNT, 10) |
+ FIELD_PREP(MT7628_ESW_DISMIIPORT_WASTX, 0) |
+ FIELD_PREP(MT7628_ESW_BP_MODE, 0b10) |
+ FIELD_PREP(MT7628_ESW_LED_FLASH_TIME, 0) |
+ FIELD_PREP(MT7628_ESW_RMC_RULE, 0) |
+ FIELD_PREP(MT7628_ESW_IP_MULT_RULE, 0));
+
+ regmap_write(esw->regmap, MT7628_ESW_REG_SOCPC,
+ MT7628_ESW_SOCPC_CRC_PADDING |
+ FIELD_PREP(MT7628_ESW_SOCPC_DISUN2CPU,
+ MT7628_ESW_PORTS_CPU) |
+ FIELD_PREP(MT7628_ESW_SOCPC_DISMC2CPU,
+ MT7628_ESW_PORTS_CPU) |
+ FIELD_PREP(MT7628_ESW_SOCPC_DISBC2CPU,
+ MT7628_ESW_PORTS_CPU));
+
+ regmap_set_bits(esw->regmap, MT7628_ESW_REG_FPA2,
+ MT7628_ESW_FPA2_FORCE_RGMII_EN1 |
+ MT7628_ESW_FPA2_FORCE_RGMII_LINK1 |
+ MT7628_ESW_FPA2_AP_EN);
+
+ regmap_update_bits(esw->regmap, MT7628_ESW_REG_FPA2,
+ MT7628_ESW_FPA2_EXT_PHY_ADDR_BASE,
+ FIELD_PREP(MT7628_ESW_FPA2_EXT_PHY_ADDR_BASE, 31));
+
+ /* disable all interrupts */
+ regmap_write(esw->regmap, MT7628_ESW_REG_IMR, 0);
+
+ /* enable MT7628 DSA tag on CPU port */
+ regmap_write(esw->regmap, MT7628_ESW_REG_SGC2,
+ MT7628_ESW_SGC2_SPECIAL_TAG_EN |
+ FIELD_PREP(MT7628_ESW_SGC2_TX_CPU_TPID_BIT_MAP,
+ MT7628_ESW_PORTS_CPU));
+
+ /*
+ * Double tag feature allows switch to always append the port PVID VLAN tag
+ * regardless of if the incoming packet already has a VLAN tag.
+ * This is enabled to simulate VLAN unawareness.
+ */
+ regmap_set_bits(esw->regmap, MT7628_ESW_REG_SGC2,
+ FIELD_PREP(MT7628_ESW_SGC2_DOUBLE_TAG_EN,
+ MT7628_ESW_PORTS_NOCPU));
+
+ regmap_set_bits(esw->regmap, MT7628_ESW_REG_POC2,
+ MT7628_ESW_POC2_PER_VLAN_UNTAG_EN);
+
+ regmap_update_bits(esw->regmap, MT7628_ESW_REG_PFC1,
+ MT7628_ESW_PFC1_EN_VLAN,
+ FIELD_PREP(MT7628_ESW_PFC1_EN_VLAN,
+ MT7628_ESW_PORTS_ALL));
+}
+
+static void mt7628_esw_set_pvid(struct mt7628_esw *esw, unsigned int port,
+ unsigned int pvid)
+{
+ regmap_update_bits(esw->regmap, MT7628_ESW_REG_PVIDC(port),
+ MT7628_ESW_PVID_MASK(port),
+ MT7628_ESW_PVID_PREP(port, pvid));
+}
+
+static void mt7628_esw_set_vlan_id(struct mt7628_esw *esw, unsigned int vlan,
+ unsigned int vid)
+{
+ regmap_update_bits(esw->regmap, MT7628_ESW_REG_VLANI(vlan),
+ MT7628_ESW_VID_MASK(vlan),
+ MT7628_ESW_VID_PREP(vlan, vid));
+}
+
+static void mt7628_esw_set_vmsc(struct mt7628_esw *esw, unsigned int vlan,
+ unsigned int msc)
+{
+ regmap_update_bits(esw->regmap, MT7628_ESW_REG_VMSC(vlan),
+ MT7628_ESW_VMSC_MASK(vlan),
+ MT7628_ESW_VMSC_PREP(vlan, msc));
+}
+
+static void mt7628_esw_set_vub(struct mt7628_esw *esw, unsigned int vlan,
+ unsigned int vub)
+{
+ regmap_update_bits(esw->regmap, MT7628_ESW_REG_VUB(vlan),
+ MT7628_ESW_VUB_MASK(vlan),
+ MT7628_ESW_VUB_PREP(vlan, vub));
+}
+
+static void mt7628_vlan_sync(struct dsa_switch *ds)
+{
+ struct mt7628_esw *esw = ds->priv;
+ int i;
+
+ for (i = 0; i < MT7628_NUM_VLANS; i++) {
+ struct mt7628_vlan *vlan = &esw->vlans[i];
+
+ mt7628_esw_set_vmsc(esw, i, vlan->members);
+ mt7628_esw_set_vlan_id(esw, i, vlan->vid);
+ mt7628_esw_set_vub(esw, i, vlan->untag);
+ }
+
+ for (i = 0; i < ds->num_ports; i++)
+ mt7628_esw_set_pvid(esw, i, esw->tag_8021q_pvid[i]);
+}
+
+static int mt7628_setup(struct dsa_switch *ds)
+{
+ struct mt7628_esw *esw = ds->priv;
+ int ret;
+
+ reset_control_reset(esw->rst_esw);
+ usleep_range(1000, 2000);
+ reset_control_reset(esw->rst_ephy);
+ usleep_range(1000, 2000);
+ /*
+ * all MMIO reads hang if esw is not out of reset
+ * ephy needs extra time to get out of reset or it ends up misconfigured
+ */
+ mt7628_switch_init(ds);
+ rtnl_lock();
+ dsa_tag_8021q_register(ds, htons(ETH_P_8021Q));
+ rtnl_unlock();
+
+ ret = mt7628_setup_internal_mdio(ds);
+ return ret;
+}
+
+static int mt7628_port_enable(struct dsa_switch *ds, int port,
+ struct phy_device *phy)
+{
+ struct mt7628_esw *esw = ds->priv;
+
+ regmap_clear_bits(esw->regmap, MT7628_ESW_REG_POC0,
+ FIELD_PREP(MT7628_ESW_POC0_PORT_DISABLE, BIT(port)));
+ return 0;
+}
+
+static void mt7628_port_disable(struct dsa_switch *ds, int port)
+{
+ struct mt7628_esw *esw = ds->priv;
+
+ regmap_set_bits(esw->regmap, MT7628_ESW_REG_POC0,
+ FIELD_PREP(MT7628_ESW_POC0_PORT_DISABLE, BIT(port)));
+}
+
+static enum dsa_tag_protocol
+mt7628_get_tag_proto(struct dsa_switch *ds, int port, enum dsa_tag_protocol mp)
+{
+ return DSA_TAG_PROTO_MT7628;
+}
+
+static void mt7628_phylink_get_caps(struct dsa_switch *ds, int port,
+ struct phylink_config *config)
+{
+ switch (port) {
+ case 6:
+ config->mac_capabilities |= MAC_1000;
+ fallthrough;
+ case 0 ... 4:
+ config->mac_capabilities |= MAC_100 | MAC_10;
+ __set_bit(PHY_INTERFACE_MODE_INTERNAL,
+ config->supported_interfaces);
+ break;
+ default:
+ break; /* port 5 does not exist on MT7628 */
+ }
+}
+
+static int mt7628_dsa_8021q_vlan_add(struct dsa_switch *ds, int port,
+ u16 vid, u16 flags)
+{
+ struct mt7628_esw *esw = ds->priv;
+ struct mt7628_vlan *vlan = NULL;
+ int i;
+
+ for (i = 0; i < MT7628_NUM_VLANS; i++) {
+ struct mt7628_vlan *check_vlan = &esw->vlans[i];
+
+ if (!check_vlan->active && !vlan) {
+ vlan = check_vlan;
+ } else if (check_vlan->vid == vid) {
+ vlan = check_vlan;
+ break;
+ }
+ }
+
+ if (!vlan)
+ return -ENOSPC;
+
+ vlan->vid = vid;
+ vlan->active = true;
+ vlan->members |= BIT(port);
+
+ if (flags & BRIDGE_VLAN_INFO_PVID)
+ esw->tag_8021q_pvid[port] = vid;
+
+ if (flags & BRIDGE_VLAN_INFO_UNTAGGED)
+ vlan->untag |= BIT(port);
+
+ mt7628_vlan_sync(ds);
+ return 0;
+}
+
+static int mt7628_dsa_8021q_vlan_del(struct dsa_switch *ds, int port, u16 vid)
+{
+ struct mt7628_esw *esw = ds->priv;
+ struct mt7628_vlan *vlan = NULL;
+ int i;
+
+ for (i = 0; i < MT7628_NUM_VLANS; i++) {
+ struct mt7628_vlan *check_vlan = &esw->vlans[i];
+
+ if (!check_vlan->active || check_vlan->vid != vid)
+ continue;
+ vlan = check_vlan;
+ break;
+ }
+ if (!vlan)
+ return -ENOENT;
+
+ vlan->members &= ~BIT(port);
+ vlan->untag &= ~BIT(port);
+
+ if (!vlan->members)
+ vlan->active = false;
+
+ mt7628_vlan_sync(ds);
+ return 0;
+}
+
+static struct dsa_switch_ops mt7628_switch_ops = {
+ .get_tag_protocol = mt7628_get_tag_proto,
+ .setup = mt7628_setup,
+ .port_enable = mt7628_port_enable,
+ .port_disable = mt7628_port_disable,
+ .phylink_get_caps = mt7628_phylink_get_caps,
+ .tag_8021q_vlan_add = mt7628_dsa_8021q_vlan_add,
+ .tag_8021q_vlan_del = mt7628_dsa_8021q_vlan_del,
+};
+
+static int mt7628_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct mt7628_esw *esw;
+ struct dsa_switch *ds;
+
+ ds = devm_kzalloc(&pdev->dev, sizeof(*ds), GFP_KERNEL);
+ if (!ds)
+ return -ENOMEM;
+
+ esw = devm_kzalloc(&pdev->dev, sizeof(*esw), GFP_KERNEL);
+ if (!esw)
+ return -ENOMEM;
+
+ esw->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(esw->base))
+ return PTR_ERR(esw->base);
+
+ esw->regmap = devm_regmap_init_mmio(&pdev->dev, esw->base,
+ &mt7628_esw_regmap_cfg);
+ if (IS_ERR(esw->regmap))
+ return PTR_ERR(esw->regmap);
+
+ esw->rst_ephy = devm_reset_control_get_exclusive(&pdev->dev, "ephy");
+ if (IS_ERR(esw->rst_ephy))
+ return dev_err_probe(dev, PTR_ERR(esw->rst_ephy),
+ "failed to get EPHY reset\n");
+
+ esw->rst_esw = devm_reset_control_get_exclusive(&pdev->dev, "esw");
+ if (IS_ERR(esw->rst_esw))
+ return dev_err_probe(dev, PTR_ERR(esw->rst_esw),
+ "failed to get ESW reset\n");
+
+ ds->dev = dev;
+ ds->num_ports = MT7628_ESW_NUM_PORTS;
+ ds->ops = &mt7628_switch_ops;
+ ds->priv = esw;
+ esw->ds = ds;
+ esw->dev = dev;
+ dev_set_drvdata(dev, esw);
+
+ return dsa_register_switch(ds);
+}
+
+static void mt7628_remove(struct platform_device *pdev)
+{
+ struct mt7628_esw *esw = platform_get_drvdata(pdev);
+
+ if (!esw)
+ return;
+
+ dsa_unregister_switch(esw->ds);
+}
+
+static void mt7628_shutdown(struct platform_device *pdev)
+{
+ struct mt7628_esw *esw = platform_get_drvdata(pdev);
+
+ if (!esw)
+ return;
+
+ dsa_switch_shutdown(esw->ds);
+ dev_set_drvdata(&pdev->dev, NULL);
+}
+
+static const struct of_device_id mt7628_of_match[] = {
+ { .compatible = "mediatek,mt7628-esw" },
+ {}
+};
+
+MODULE_DEVICE_TABLE(of, mt7628_of_match);
+
+static struct platform_driver mt7628_driver = {
+ .driver = {
+ .name = "mt7628-esw",
+ .of_match_table = mt7628_of_match,
+ },
+ .probe = mt7628_probe,
+ .remove = mt7628_remove,
+ .shutdown = mt7628_shutdown,
+};
+
+module_platform_driver(mt7628_driver);
+
+MODULE_AUTHOR("Joris Vaisvila <joey@tinyisr.com>");
+MODULE_DESCRIPTION("Driver for Mediatek MT7628 embedded switch");
+MODULE_LICENSE("GPL");
--
2.53.0
^ permalink raw reply related
* [PATCH net-next v2 3/4] net: dsa: initial MT7628 tagging driver
From: Joris Vaisvila @ 2026-03-30 18:40 UTC (permalink / raw)
To: netdev
Cc: horms, pabeni, kuba, edumazet, davem, olteanv, Andrew Lunn,
devicetree, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Joris Vaisvila
In-Reply-To: <20260330184017.766200-1-joey@tinyisr.com>
Add support for the MT7628 embedded switch's tag.
The MT7628 tag is merged with the VLAN TPID field when a VLAN is
appended by the switch hardware. It is not installed if the VLAN tag is
already there on ingress. Due to this hardware quirk the tag cannot be
trusted for port 0 if we don't know that the VLAN was added by the
hardware. As a workaround for this the switch is configured to always
append the port PVID tag even if the incoming packet is already tagged.
The tagging driver can then trust that the tag is always accurate and
the whole VLAN tag can be removed on ingress as it's only metadata for
the tagger.
On egress the MT7628 tag allows precise TX, but the correct VLAN tag
from tag_8021q is still appended or the switch will not forward the
packet.
Signed-off-by: Joris Vaisvila <joey@tinyisr.com>
---
include/net/dsa.h | 2 +
net/dsa/Kconfig | 6 +++
net/dsa/Makefile | 1 +
net/dsa/tag_mt7628.c | 89 ++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 98 insertions(+)
create mode 100644 net/dsa/tag_mt7628.c
diff --git a/include/net/dsa.h b/include/net/dsa.h
index 6c17446f3dcc..e93f9356b5c3 100644
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -58,6 +58,7 @@ struct tc_action;
#define DSA_TAG_PROTO_YT921X_VALUE 30
#define DSA_TAG_PROTO_MXL_GSW1XX_VALUE 31
#define DSA_TAG_PROTO_MXL862_VALUE 32
+#define DSA_TAG_PROTO_MT7628_VALUE 33
enum dsa_tag_protocol {
DSA_TAG_PROTO_NONE = DSA_TAG_PROTO_NONE_VALUE,
@@ -93,6 +94,7 @@ enum dsa_tag_protocol {
DSA_TAG_PROTO_YT921X = DSA_TAG_PROTO_YT921X_VALUE,
DSA_TAG_PROTO_MXL_GSW1XX = DSA_TAG_PROTO_MXL_GSW1XX_VALUE,
DSA_TAG_PROTO_MXL862 = DSA_TAG_PROTO_MXL862_VALUE,
+ DSA_TAG_PROTO_MT7628 = DSA_TAG_PROTO_MT7628_VALUE,
};
struct dsa_switch;
diff --git a/net/dsa/Kconfig b/net/dsa/Kconfig
index 5ed8c704636d..946f44f0b843 100644
--- a/net/dsa/Kconfig
+++ b/net/dsa/Kconfig
@@ -98,6 +98,12 @@ config NET_DSA_TAG_EDSA
Say Y or M if you want to enable support for tagging frames for the
Marvell switches which use EtherType DSA headers.
+config NET_DSA_TAG_MT7628
+ tristate "Tag driver for the MT7628 embedded switch"
+ help
+ Say Y or M if you want to enable support for tagging frames for the
+ switch embedded in the MT7628 SoC.
+
config NET_DSA_TAG_MTK
tristate "Tag driver for Mediatek switches"
help
diff --git a/net/dsa/Makefile b/net/dsa/Makefile
index bf7247759a64..a84f33f0963d 100644
--- a/net/dsa/Makefile
+++ b/net/dsa/Makefile
@@ -27,6 +27,7 @@ obj-$(CONFIG_NET_DSA_TAG_GSWIP) += tag_gswip.o
obj-$(CONFIG_NET_DSA_TAG_HELLCREEK) += tag_hellcreek.o
obj-$(CONFIG_NET_DSA_TAG_KSZ) += tag_ksz.o
obj-$(CONFIG_NET_DSA_TAG_LAN9303) += tag_lan9303.o
+obj-$(CONFIG_NET_DSA_TAG_MT7628) += tag_mt7628.o
obj-$(CONFIG_NET_DSA_TAG_MTK) += tag_mtk.o
obj-$(CONFIG_NET_DSA_TAG_MXL_862XX) += tag_mxl862xx.o
obj-$(CONFIG_NET_DSA_TAG_MXL_GSW1XX) += tag_mxl-gsw1xx.o
diff --git a/net/dsa/tag_mt7628.c b/net/dsa/tag_mt7628.c
new file mode 100644
index 000000000000..f0e346595f30
--- /dev/null
+++ b/net/dsa/tag_mt7628.c
@@ -0,0 +1,89 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026, Joris Vaisvila <joey@tinyisr.com>
+ * MT7628 switch tag support
+ */
+
+#include <linux/etherdevice.h>
+#include <linux/dsa/8021q.h>
+#include <net/dsa.h>
+
+#include "tag.h"
+
+/*
+ * The MT7628 tag is encoded in the VLAN TPID field.
+ * On TX the lower 6 bits encode the destination port bitmask.
+ * On RX the lower 3 bits encode the source port number.
+ *
+ * The switch hardware will not modify the TPID of an incoming packet if it is
+ * already VLAN tagged. To work around this the switch is configured to always
+ * append a tag_8021q standalone VLAN tag for each port. That means we can
+ * safely strip the outer VLAN tag after parsing it.
+ *
+ * A VLAN tag is constructed on egress to target the standalone VLAN and
+ * destination port.
+ */
+
+#define MT7628_TAG_NAME "mt7628"
+
+#define MT7628_TAG_TX_PORT GENMASK(5, 0)
+#define MT7628_TAG_RX_PORT GENMASK(2, 0)
+#define MT7628_TAG_LEN 4
+
+static struct sk_buff *mt7628_tag_xmit(struct sk_buff *skb,
+ struct net_device *dev)
+{
+ struct dsa_port *dp;
+ u16 xmit_vlan;
+ __be16 *tag;
+
+ dp = dsa_user_to_port(dev);
+ xmit_vlan = dsa_tag_8021q_standalone_vid(dp);
+
+ skb_push(skb, MT7628_TAG_LEN);
+ dsa_alloc_etype_header(skb, MT7628_TAG_LEN);
+
+ tag = dsa_etype_header_pos_tx(skb);
+
+ tag[0] = htons(ETH_P_8021Q |
+ FIELD_PREP(MT7628_TAG_TX_PORT,
+ dsa_xmit_port_mask(skb, dev)));
+ tag[1] = htons(xmit_vlan);
+
+ return skb;
+}
+
+static struct sk_buff *mt7628_tag_rcv(struct sk_buff *skb,
+ struct net_device *dev)
+{
+ __be16 *phdr;
+
+ if (unlikely(!pskb_may_pull(skb, MT7628_TAG_LEN)))
+ return NULL;
+
+ phdr = dsa_etype_header_pos_rx(skb);
+ skb->dev =
+ dsa_conduit_find_user(dev, 0,
+ FIELD_GET(MT7628_TAG_RX_PORT, ntohs(*phdr)));
+ if (!skb->dev)
+ return NULL;
+
+ skb_pull_rcsum(skb, MT7628_TAG_LEN);
+ dsa_strip_etype_header(skb, MT7628_TAG_LEN);
+ dsa_default_offload_fwd_mark(skb);
+ return skb;
+}
+
+static const struct dsa_device_ops mt7628_tag_ops = {
+ .name = MT7628_TAG_NAME,
+ .proto = DSA_TAG_PROTO_MT7628,
+ .xmit = mt7628_tag_xmit,
+ .rcv = mt7628_tag_rcv,
+ .needed_headroom = MT7628_TAG_LEN,
+};
+
+module_dsa_tag_driver(mt7628_tag_ops);
+
+MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_MT7628, MT7628_TAG_NAME);
+MODULE_DESCRIPTION("DSA tag driver for MT7628 switch");
+MODULE_LICENSE("GPL");
--
2.53.0
^ permalink raw reply related
* [PATCH net-next v2 2/4] net: phy: mediatek: add phy driver for MT7628 built-in Fast Ethernet PHYs
From: Joris Vaisvila @ 2026-03-30 18:40 UTC (permalink / raw)
To: netdev
Cc: horms, pabeni, kuba, edumazet, davem, olteanv, Andrew Lunn,
devicetree, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Joris Vaisvila
In-Reply-To: <20260330184017.766200-1-joey@tinyisr.com>
The Fast Ethernet PHYs present in the MT7628 SoCs require an
undocumented bit to be set before they can establish 100mbps links.
This commit adds the Kconfig option MEDIATEK_FE_SOC_PHY and the
corresponding driver mtk-fe-soc.c.
Signed-off-by: Joris Vaisvila <joey@tinyisr.com>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
---
drivers/net/phy/mediatek/Kconfig | 10 +++++-
drivers/net/phy/mediatek/Makefile | 1 +
drivers/net/phy/mediatek/mtk-fe-soc.c | 50 +++++++++++++++++++++++++++
3 files changed, 60 insertions(+), 1 deletion(-)
create mode 100644 drivers/net/phy/mediatek/mtk-fe-soc.c
diff --git a/drivers/net/phy/mediatek/Kconfig b/drivers/net/phy/mediatek/Kconfig
index bb7dc876271e..b6a51f38c358 100644
--- a/drivers/net/phy/mediatek/Kconfig
+++ b/drivers/net/phy/mediatek/Kconfig
@@ -21,8 +21,16 @@ config MEDIATEK_GE_PHY
common operations with MediaTek SoC built-in Gigabit
Ethernet PHYs.
+config MEDIATEK_FE_SOC_PHY
+ tristate "MediaTek SoC Fast Ethernet PHYs"
+ help
+ Support for MediaTek MT7628 built-in Fast Ethernet PHYs.
+ This driver only sets an initialization bit required for the PHY
+ to establish 100 Mbps links. All other PHY operations are handled
+ by the kernel's generic PHY code.
+
config MEDIATEK_GE_SOC_PHY
- tristate "MediaTek SoC Ethernet PHYs"
+ tristate "MediaTek SoC Gigabit Ethernet PHYs"
depends on ARM64 || COMPILE_TEST
depends on ARCH_AIROHA || (ARCH_MEDIATEK && NVMEM_MTK_EFUSE) || \
COMPILE_TEST
diff --git a/drivers/net/phy/mediatek/Makefile b/drivers/net/phy/mediatek/Makefile
index ac57ecc799fc..6f9cacf7f906 100644
--- a/drivers/net/phy/mediatek/Makefile
+++ b/drivers/net/phy/mediatek/Makefile
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_MEDIATEK_2P5GE_PHY) += mtk-2p5ge.o
+obj-$(CONFIG_MEDIATEK_FE_SOC_PHY) += mtk-fe-soc.o
obj-$(CONFIG_MEDIATEK_GE_PHY) += mtk-ge.o
obj-$(CONFIG_MEDIATEK_GE_SOC_PHY) += mtk-ge-soc.o
obj-$(CONFIG_MTK_NET_PHYLIB) += mtk-phy-lib.o
diff --git a/drivers/net/phy/mediatek/mtk-fe-soc.c b/drivers/net/phy/mediatek/mtk-fe-soc.c
new file mode 100644
index 000000000000..317944411fbe
--- /dev/null
+++ b/drivers/net/phy/mediatek/mtk-fe-soc.c
@@ -0,0 +1,50 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for MT7628 Embedded Switch internal Fast Ethernet PHYs
+ */
+#include <linux/module.h>
+#include <linux/phy.h>
+
+#define MTK_FPHY_ID_MT7628 0x03a29410
+#define MTK_EXT_PAGE_ACCESS 0x1f
+
+static int mt7628_phy_read_page(struct phy_device *phydev)
+{
+ return __phy_read(phydev, MTK_EXT_PAGE_ACCESS);
+}
+
+static int mt7628_phy_write_page(struct phy_device *phydev, int page)
+{
+ return __phy_write(phydev, MTK_EXT_PAGE_ACCESS, page);
+}
+
+static int mt7628_phy_config_init(struct phy_device *phydev)
+{
+ /*
+ * This undocumented bit is required for the PHYs to be able to
+ * establish 100mbps links.
+ */
+ return phy_write_paged(phydev, 0x8000, 30, BIT(13));
+}
+
+static struct phy_driver mtk_soc_fe_phy_driver[] = {
+ {
+ PHY_ID_MATCH_EXACT(MTK_FPHY_ID_MT7628),
+ .name = "MediaTek MT7628 PHY",
+ .config_init = mt7628_phy_config_init,
+ .read_page = mt7628_phy_read_page,
+ .write_page = mt7628_phy_write_page,
+ },
+};
+
+module_phy_driver(mtk_soc_fe_phy_driver);
+static const struct mdio_device_id __maybe_unused mtk_soc_fe_phy_tbl[] = {
+ { PHY_ID_MATCH_EXACT(MTK_FPHY_ID_MT7628) },
+ { }
+};
+
+MODULE_DESCRIPTION("MediaTek SoC Fast Ethernet PHY driver");
+MODULE_AUTHOR("Joris Vaisvila <joey@tinyisr.com>");
+MODULE_LICENSE("GPL");
+
+MODULE_DEVICE_TABLE(mdio, mtk_soc_fe_phy_tbl);
--
2.53.0
^ permalink raw reply related
* [PATCH net-next v2 1/4] dt-bindings: net: dsa: add MT7628 ESW
From: Joris Vaisvila @ 2026-03-30 18:40 UTC (permalink / raw)
To: netdev
Cc: horms, pabeni, kuba, edumazet, davem, olteanv, Andrew Lunn,
devicetree, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Joris Vaisvila
In-Reply-To: <20260330184017.766200-1-joey@tinyisr.com>
Add bindings for MT7628 SoC's Embedded Switch.
Signed-off-by: Joris Vaisvila <joey@tinyisr.com>
---
.../bindings/net/dsa/mediatek,mt7628-esw.yaml | 101 ++++++++++++++++++
1 file changed, 101 insertions(+)
create mode 100644 Documentation/devicetree/bindings/net/dsa/mediatek,mt7628-esw.yaml
diff --git a/Documentation/devicetree/bindings/net/dsa/mediatek,mt7628-esw.yaml b/Documentation/devicetree/bindings/net/dsa/mediatek,mt7628-esw.yaml
new file mode 100644
index 000000000000..d6c66ab677d0
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/dsa/mediatek,mt7628-esw.yaml
@@ -0,0 +1,101 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/dsa/mediatek,mt7628-esw.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Mediatek MT7628 Embedded Ethernet Switch
+
+maintainers:
+ - Joris Vaisvila <joey@tinyisr.com>
+
+description:
+ The MT7628 SoC's built-in Ethernet Switch is a five port switch with
+ integrated 10/100 PHYs. The switch registers are directly mapped in the SoC's
+ memory. The switch has an internally connected 1G CPU port and 5 user ports
+ connected to the built-in Fast Ethernet PHYs.
+
+unevaluatedProperties: false
+
+allOf:
+ - $ref: dsa.yaml#/$defs/ethernet-ports
+
+properties:
+ compatible:
+ const: mediatek,mt7628-esw
+
+ reg:
+ maxItems: 1
+ description: MMIO address of the switch
+
+ resets:
+ items:
+ - description: Phandle of system reset controller with ESW reset index
+ - description: Phandle of system reset controller with EPHY reset index
+
+ reset-names:
+ items:
+ - const: esw
+ - const: ephy
+
+ mdio:
+ $ref: /schemas/net/mdio.yaml#
+ unevaluatedProperties: false
+
+required:
+ - compatible
+ - reg
+ - resets
+ - reset-names
+
+examples:
+ - |
+ switch0: switch@10110000 {
+ reg = <0x10110000 0x8000>;
+
+ resets = <&sysc 23>, <&sysc 24>;
+ reset-names = "esw", "ephy";
+
+ compatible = "mediatek,mt7628-esw";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ phy-mode = "internal";
+ };
+
+ port@1 {
+ reg = <1>;
+ phy-mode = "internal";
+ };
+
+ port@2 {
+ reg = <2>;
+ phy-mode = "internal";
+ };
+
+ port@3 {
+ reg = <3>;
+ phy-mode = "internal";
+ };
+
+ port@4 {
+ reg = <4>;
+ phy-mode = "internal";
+ };
+
+ port@6 {
+ reg = <6>;
+ phy-mode = "internal";
+ ethernet = <ðernet>;
+
+ fixed-link {
+ speed = <1000>;
+ full-duplex;
+ };
+ };
+ };
+ };
--
2.53.0
^ permalink raw reply related
* [PATCH net-next v2 0/4] net: dsa: mt7628 embedded switch initial support
From: Joris Vaisvila @ 2026-03-30 18:40 UTC (permalink / raw)
To: netdev
Cc: horms, pabeni, kuba, edumazet, davem, olteanv, Andrew Lunn,
devicetree, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Joris Vaisvila
Hello,
This patch series adds initial support for the MediaTek MT7628 Embedded
Switch.
The driver implements the basic functionality required to operate the
switch using DSA. The hardware provides five internal Fast Ethernet user
ports and one Gigabit port connected internally to the CPU MAC.
Bridge offloading is not yet supported.
Tested on an MT7628NN-based board.
changes since v1:
- changed port 6 phy-mode to internal
- cleaned up tag_mt7628 rcv function and mask defines
- fixed sorting error in drivers/net/dsa/ Kconfig and Makefile
- fixed sorting error in net/dsa/ Kconfig and Makefile
- fixed mt7628_mii_read/write return values on error
Link: https://lore.kernel.org/netdev/20260326204413.3317584-1-joey@tinyisr.com/t/#u
Thanks,
Joris
Joris Vaisvila (4):
dt-bindings: net: dsa: add MT7628 ESW
net: phy: mediatek: add phy driver for MT7628 built-in Fast Ethernet
PHYs
net: dsa: initial MT7628 tagging driver
net: dsa: initial support for MT7628 embedded switch
.../bindings/net/dsa/mediatek,mt7628-esw.yaml | 101 +++
drivers/net/dsa/Kconfig | 8 +
drivers/net/dsa/Makefile | 1 +
drivers/net/dsa/mt7628.c | 627 ++++++++++++++++++
drivers/net/phy/mediatek/Kconfig | 10 +-
drivers/net/phy/mediatek/Makefile | 1 +
drivers/net/phy/mediatek/mtk-fe-soc.c | 50 ++
include/net/dsa.h | 2 +
net/dsa/Kconfig | 6 +
net/dsa/Makefile | 1 +
net/dsa/tag_mt7628.c | 89 +++
11 files changed, 895 insertions(+), 1 deletion(-)
create mode 100644 Documentation/devicetree/bindings/net/dsa/mediatek,mt7628-esw.yaml
create mode 100644 drivers/net/dsa/mt7628.c
create mode 100644 drivers/net/phy/mediatek/mtk-fe-soc.c
create mode 100644 net/dsa/tag_mt7628.c
--
2.53.0
^ permalink raw reply
* Re: [PATCH net-next v4 0/2] e1000e: add XDP support
From: Matteo Croce @ 2026-03-30 18:23 UTC (permalink / raw)
To: Maciej Fijalkowski
Cc: Tony Nguyen, Przemek Kitszel, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Alexei Starovoitov,
Daniel Borkmann, Jesper Dangaard Brouer, John Fastabend,
Mohsin Bashir, netdev, bpf, intel-wired-lan, linux-kernel
In-Reply-To: <acp4BblnJfpVClct@boxer>
Il giorno lun 30 mar 2026 alle ore 15:18 Maciej Fijalkowski
<maciej.fijalkowski@intel.com> ha scritto:
>
> On Mon, Mar 23, 2026 at 07:28:21PM +0100, Matteo Croce wrote:
> > Add XDP support to e1000e driver. First patch adds basic XDP support,
> > while the second one adds support for AF_XDP.
>
> This set has nothing to do with AF_XDP.
>
Should I mention XDP_REDIRECT instead?
--
Matteo Croce
perl -e 'for($t=0;;$t++){print chr($t*($t>>8|$t>>13)&255)}' |aplay
^ permalink raw reply
* Re: [PATCH net-next v3 4/6] net: bcmgenet: add XDP_TX support
From: Mohsin Bashir @ 2026-03-30 18:15 UTC (permalink / raw)
To: Nicolai Buchwitz, netdev
Cc: Doug Berger, Florian Fainelli,
Broadcom internal kernel review list, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Alexei Starovoitov, Daniel Borkmann, Jesper Dangaard Brouer,
John Fastabend, Stanislav Fomichev, linux-kernel, bpf
In-Reply-To: <20260319115402.353509-5-nb@tipi-net.de>
> +static bool
> +bcmgenet_xdp_xmit_frame(struct bcmgenet_priv *priv,
> + struct xdp_frame *xdpf, bool dma_map)
> +{
> + struct bcmgenet_tx_ring *ring = &priv->tx_rings[DESC_INDEX];
> + struct device *kdev = &priv->pdev->dev;
> + struct enet_cb *tx_cb_ptr;
> + dma_addr_t mapping;
> + unsigned int dma_len;
> + u32 len_stat;
> +
> + spin_lock(&ring->lock);
> +
So we acquire lock on per-packet basis on the XDP_TX path? Do you think
we can batch this?
^ permalink raw reply
* Re: [PATCH net-next v3 3/6] net: bcmgenet: add basic XDP support (PASS/DROP)
From: Mohsin Bashir @ 2026-03-30 18:15 UTC (permalink / raw)
To: Nicolai Buchwitz, netdev
Cc: Doug Berger, Florian Fainelli,
Broadcom internal kernel review list, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Alexei Starovoitov, Daniel Borkmann, Jesper Dangaard Brouer,
John Fastabend, Stanislav Fomichev, linux-kernel, bpf
In-Reply-To: <20260319115402.353509-4-nb@tipi-net.de>
> +static int bcmgenet_xdp_setup(struct net_device *dev,
> + struct netdev_bpf *xdp)
> +{
> + struct bcmgenet_priv *priv = netdev_priv(dev);
> + struct bpf_prog *old_prog;
> + struct bpf_prog *prog = xdp->prog;
> +
> + if (prog && dev->mtu > PAGE_SIZE - GENET_RX_HEADROOM -
> + SKB_DATA_ALIGN(sizeof(struct skb_shared_info))) {
> + NL_SET_ERR_MSG_MOD(xdp->extack,
> + "MTU too large for single-page XDP buffer");
> + return -EOPNOTSUPP;
> + }
The MTU check here is great. But I do not see support for
.ndo_change_mtu. This would allow users to change MTU size AFTER the
program is attached.
^ permalink raw reply
* Re: [PATCH net-next v3 1/6] net: bcmgenet: convert RX path to page_pool
From: Mohsin Bashir @ 2026-03-30 18:15 UTC (permalink / raw)
To: Nicolai Buchwitz, netdev
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Doug Berger, Florian Fainelli,
Broadcom internal kernel review list, Vikas Gupta,
Rajashekar Hudumula, Bhargava Marreddy, Eric Biggers,
Arnd Bergmann, linux-kernel
In-Reply-To: <20260319115402.353509-2-nb@tipi-net.de>
> if (priv->crc_fwd_en) {
> - skb_trim(skb, len - ETH_FCS_LEN);
> + skb_trim(skb, skb->len - ETH_FCS_LEN);
> len -= ETH_FCS_LEN;
Looks like 'len' update here is unnecessary since we are overwriting it
later anyway?
> }
>
> + /* Set up checksum offload */
> + if (dev->features & NETIF_F_RXCSUM) {
> + rx_csum = (__force __be16)(status->rx_csum & 0xffff);
> + if (rx_csum) {
> + skb->csum = (__force __wsum)ntohs(rx_csum);
> + skb->ip_summed = CHECKSUM_COMPLETE;
> + }
> + }
> +
> + len = skb->len;
^ permalink raw reply
* [net-next v36] mctp pcc: Implement MCTP over PCC Transport
From: Adam Young @ 2026-03-30 17:54 UTC (permalink / raw)
To: Jeremy Kerr, Matt Johnston, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni
Cc: netdev, linux-kernel, Sudeep Holla, Jonathan Cameron, Huisong Li
Implementation of network driver for
Management Component Transport Protocol(MCTP)
over Platform Communication Channel(PCC)
DMTF DSP:0292
Link: https://www.dmtf.org/sites/default/files/standards/documents/DSP0292_1.0.0WIP50.pdf
The transport mechanism is called Platform Communication Channels (PCC)
is part of the ACPI spec:
Link: https://uefi.org/htmlspecs/ACPI_Spec_6_4_html/14_Platform_Communications_Channel/Platform_Comm_Channel.html
The PCC mechanism is managed via a mailbox implemented at
drivers/mailbox/pcc.c
MCTP devices are specified via ACPI by entries in DSDT/SSDT and
reference channels specified in the PCCT. Messages are sent on a type
3 and received on a type 4 channel. Communication with other devices
use the PCC based doorbell mechanism; a shared memory segment with a
corresponding interrupt and a memory register used to trigger remote
interrupts.
The shared buffer must be at least 68 bytes long as that is the minimum
MTU as defined by the MCTP specification.
Unlike the existing PCC Type 2 based drivers, the mssg parameter to
mbox_send_msg is actively used. The data section of the struct sk_buff
that contains the outgoing packet is sent to the mailbox, already
properly formatted as a PCC exctended message.
If the mailbox ring buffer is full, the driver stops the incoming
packet queues until a message has been sent, freeing space in the
ring buffer.
When the Type 3 channel outbox receives a txdone response interrupt,
it consumes the outgoing sk_buff, allowing it to be freed.
Bringing up an interface creates the channel between the network driver
and the mailbox driver. This enables communication with the remote
endpoint, to include the receipt of new messages. Bringing down an
interface removes the channel, and no new messages can be delivered.
Stopping the interface will leave any packets that are cached in the
mailbox ringbuffer. They cannot safely be freed until the PCC mailbox
attempts to deliver them and has removed them from the ring buffer.
PCC is based on a shared buffer and a set of I/O mapped memory locations
that the Spec calls registers. This mechanism exists regardless of the
existence of the driver. If the user has the ability to map these
physical location to virtual locations, they have the ability to drive the
hardware. Thus, there is a security aspect to this mechanism that extends
beyond the responsibilities of the operating system.
If the hardware does not expose the PCC in the ACPI table, this device
will never be enabled. Thus it is only an issue on hardware that does
support PCC. In that case, it is up to the remote controller to sanitize
communication; MCTP will be exposed as a socket interface, and userland
can send any crafted packet it wants. It would also be incumbent on
the hardware manufacturer to allow the end user to disable MCTP over PCC
communication if they did not want to expose it.
Signed-off-by: Adam Young <admiyo@os.amperecomputing.com>
---
MAINTAINERS | 5 +
drivers/net/mctp/Kconfig | 14 ++
drivers/net/mctp/Makefile | 1 +
drivers/net/mctp/mctp-pcc.c | 345 ++++++++++++++++++++++++++++++++++++
4 files changed, 365 insertions(+)
create mode 100644 drivers/net/mctp/mctp-pcc.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 7a2ffd9d37d5..1813a2126cc0 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15288,6 +15288,11 @@ F: include/net/mctpdevice.h
F: include/net/netns/mctp.h
F: net/mctp/
+MANAGEMENT COMPONENT TRANSPORT PROTOCOL (MCTP) over PCC (MCTP-PCC) Driver
+M: Adam Young <admiyo@os.amperecomputing.com>
+S: Maintained
+F: drivers/net/mctp/mctp-pcc.c
+
MAPLE TREE
M: Liam R. Howlett <Liam.Howlett@oracle.com>
R: Alice Ryhl <aliceryhl@google.com>
diff --git a/drivers/net/mctp/Kconfig b/drivers/net/mctp/Kconfig
index cf325ab0b1ef..4503152c70bc 100644
--- a/drivers/net/mctp/Kconfig
+++ b/drivers/net/mctp/Kconfig
@@ -47,6 +47,20 @@ config MCTP_TRANSPORT_I3C
A MCTP protocol network device is created for each I3C bus
having a "mctp-controller" devicetree property.
+config MCTP_TRANSPORT_PCC
+ tristate "MCTP PCC transport"
+ depends on ACPI
+ depends on PCC
+ help
+ Provides a driver to access MCTP devices over PCC transport,
+ A MCTP protocol network device is created via ACPI for each
+ entry in the DSDT/SSDT that matches the identifier. The Platform
+ communication channels are selected from the corresponding
+ entries in the PCCT.
+
+ Say y here if you need to connect to MCTP endpoints over PCC. To
+ compile as a module, use m; the module will be called mctp-pcc.
+
config MCTP_TRANSPORT_USB
tristate "MCTP USB transport"
depends on USB
diff --git a/drivers/net/mctp/Makefile b/drivers/net/mctp/Makefile
index c36006849a1e..0a591299ffa9 100644
--- a/drivers/net/mctp/Makefile
+++ b/drivers/net/mctp/Makefile
@@ -1,4 +1,5 @@
obj-$(CONFIG_MCTP_SERIAL) += mctp-serial.o
obj-$(CONFIG_MCTP_TRANSPORT_I2C) += mctp-i2c.o
obj-$(CONFIG_MCTP_TRANSPORT_I3C) += mctp-i3c.o
+obj-$(CONFIG_MCTP_TRANSPORT_PCC) += mctp-pcc.o
obj-$(CONFIG_MCTP_TRANSPORT_USB) += mctp-usb.o
diff --git a/drivers/net/mctp/mctp-pcc.c b/drivers/net/mctp/mctp-pcc.c
new file mode 100644
index 000000000000..700da39aa406
--- /dev/null
+++ b/drivers/net/mctp/mctp-pcc.c
@@ -0,0 +1,345 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * mctp-pcc.c - Driver for MCTP over PCC.
+ * Copyright (c) 2024-2026, Ampere Computing LLC
+ *
+ */
+
+/* Implementation of MCTP over PCC DMTF Specification DSP0256
+ * https://www.dmtf.org/sites/default/files/standards/documents/DSP0292_1.0.0WIP50.pdf
+ */
+
+#include <linux/acpi.h>
+#include <linux/hrtimer.h>
+#include <linux/if_arp.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/mailbox_client.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/platform_device.h>
+#include <linux/skbuff.h>
+#include <linux/string.h>
+
+#include <acpi/acpi_bus.h>
+#include <acpi/acpi_drivers.h>
+#include <acpi/acrestyp.h>
+#include <acpi/actbl.h>
+#include <acpi/pcc.h>
+#include <net/mctp.h>
+#include <net/mctpdevice.h>
+
+#define MCTP_SIGNATURE "MCTP"
+#define MCTP_SIGNATURE_LENGTH (sizeof(MCTP_SIGNATURE) - 1)
+#define MCTP_MIN_MTU 68
+#define PCC_DWORD_TYPE 0x0c
+
+struct mctp_pcc_mailbox {
+ u32 index;
+ struct pcc_mbox_chan *chan;
+ struct mbox_client client;
+};
+
+/* The netdev structure. One of these per PCC adapter. */
+struct mctp_pcc_ndev {
+ struct net_device *ndev;
+ struct acpi_device *acpi_device;
+ struct mctp_pcc_mailbox inbox;
+ struct mctp_pcc_mailbox outbox;
+};
+
+static void mctp_pcc_client_rx_callback(struct mbox_client *cl, void *mssg)
+{
+ struct acpi_pcct_ext_pcc_shared_memory pcc_header;
+ struct mctp_pcc_ndev *mctp_pcc_ndev;
+ struct mctp_pcc_mailbox *inbox;
+ struct mctp_skb_cb *cb;
+ struct sk_buff *skb;
+ int size;
+
+ mctp_pcc_ndev = container_of(cl, struct mctp_pcc_ndev, inbox.client);
+ inbox = &mctp_pcc_ndev->inbox;
+ memcpy_fromio(&pcc_header, inbox->chan->shmem, sizeof(pcc_header));
+
+ // The message must at least have the PCC command indicating it is an MCTP
+ // message followed by the MCTP header, or we have a malformed message.
+ if (pcc_header.length < sizeof(pcc_header.command) + sizeof(struct mctp_hdr)) {
+ dev_dstats_rx_dropped(mctp_pcc_ndev->ndev);
+ return;
+ }
+
+ size = pcc_header.length - sizeof(pcc_header.command) + sizeof(pcc_header);
+ // If the reported size is larger than the shared memory, something is wrong
+ // and the best we can do is treat it as corrupted data.
+ if (size > inbox->chan->shmem_size) {
+ dev_dstats_rx_dropped(mctp_pcc_ndev->ndev);
+ return;
+ }
+
+ if (memcmp(&pcc_header.command, MCTP_SIGNATURE, MCTP_SIGNATURE_LENGTH) != 0) {
+ dev_dstats_rx_dropped(mctp_pcc_ndev->ndev);
+ return;
+ }
+
+ skb = netdev_alloc_skb(mctp_pcc_ndev->ndev, size);
+ if (!skb) {
+ dev_dstats_rx_dropped(mctp_pcc_ndev->ndev);
+ return;
+ }
+ skb_put(skb, size);
+ skb->protocol = htons(ETH_P_MCTP);
+ memcpy_fromio(skb->data, inbox->chan->shmem, size);
+ dev_dstats_rx_add(mctp_pcc_ndev->ndev, size);
+ skb_pull(skb, sizeof(pcc_header));
+ skb_reset_mac_header(skb);
+ skb_reset_network_header(skb);
+ cb = __mctp_cb(skb);
+ cb->halen = 0;
+ netif_rx(skb);
+}
+
+static netdev_tx_t mctp_pcc_tx(struct sk_buff *skb, struct net_device *ndev)
+{
+ struct acpi_pcct_ext_pcc_shared_memory *pcc_header;
+ struct mctp_pcc_ndev *mpnd = netdev_priv(ndev);
+ int len = skb->len;
+ int rc;
+
+ rc = skb_cow_head(skb, sizeof(*pcc_header));
+ if (rc) {
+ dev_dstats_tx_dropped(ndev);
+ kfree_skb(skb);
+ return NETDEV_TX_OK;
+ }
+
+ pcc_header = skb_push(skb, sizeof(*pcc_header));
+ pcc_header->signature = PCC_SIGNATURE | mpnd->outbox.index;
+ pcc_header->flags = PCC_CMD_COMPLETION_NOTIFY;
+ memcpy(&pcc_header->command, MCTP_SIGNATURE, MCTP_SIGNATURE_LENGTH);
+ pcc_header->length = len + MCTP_SIGNATURE_LENGTH;
+
+ rc = mbox_send_message(mpnd->outbox.chan->mchan, skb);
+ if (rc < 0) {
+ //Remove the header in case it gets sent again
+ skb_pull(skb, sizeof(*pcc_header));
+ netif_stop_queue(ndev);
+ return NETDEV_TX_BUSY;
+ }
+
+ return NETDEV_TX_OK;
+}
+
+static void mctp_pcc_tx_prepare(struct mbox_client *cl, void *mssg)
+{
+ struct mctp_pcc_ndev *mctp_pcc_ndev;
+ struct mctp_pcc_mailbox *outbox;
+ struct sk_buff *skb = mssg;
+
+ mctp_pcc_ndev = container_of(cl, struct mctp_pcc_ndev, outbox.client);
+ outbox = &mctp_pcc_ndev->outbox;
+
+ if (!skb)
+ return;
+
+ if (skb->len > outbox->chan->shmem_size) {
+ dev_dstats_tx_dropped(mctp_pcc_ndev->ndev);
+ return;
+ }
+ memcpy_toio(outbox->chan->shmem, skb->data, skb->len);
+}
+
+static void mctp_pcc_tx_done(struct mbox_client *c, void *mssg, int r)
+{
+ struct mctp_pcc_ndev *mctp_pcc_ndev;
+ struct sk_buff *skb = mssg;
+
+ mctp_pcc_ndev = container_of(c, struct mctp_pcc_ndev, outbox.client);
+ dev_dstats_tx_add(mctp_pcc_ndev->ndev, skb->len);
+ dev_consume_skb_any(skb);
+ netif_wake_queue(mctp_pcc_ndev->ndev);
+}
+
+static int mctp_pcc_ndo_open(struct net_device *ndev)
+{
+ struct mctp_pcc_ndev *mctp_pcc_ndev = netdev_priv(ndev);
+ struct mctp_pcc_mailbox *outbox, *inbox;
+
+ outbox = &mctp_pcc_ndev->outbox;
+ inbox = &mctp_pcc_ndev->inbox;
+
+ outbox->chan = pcc_mbox_request_channel(&outbox->client, outbox->index);
+ if (IS_ERR(outbox->chan))
+ return PTR_ERR(outbox->chan);
+
+ inbox->client.rx_callback = mctp_pcc_client_rx_callback;
+ inbox->chan = pcc_mbox_request_channel(&inbox->client, inbox->index);
+ if (IS_ERR(inbox->chan)) {
+ pcc_mbox_free_channel(outbox->chan);
+ return PTR_ERR(inbox->chan);
+ }
+ return 0;
+}
+
+static int mctp_pcc_ndo_stop(struct net_device *ndev)
+{
+ struct mctp_pcc_ndev *mctp_pcc_ndev = netdev_priv(ndev);
+
+ pcc_mbox_free_channel(mctp_pcc_ndev->outbox.chan);
+ pcc_mbox_free_channel(mctp_pcc_ndev->inbox.chan);
+ return 0;
+}
+
+static const struct net_device_ops mctp_pcc_netdev_ops = {
+ .ndo_open = mctp_pcc_ndo_open,
+ .ndo_stop = mctp_pcc_ndo_stop,
+ .ndo_start_xmit = mctp_pcc_tx,
+};
+
+static void mctp_pcc_setup(struct net_device *ndev)
+{
+ ndev->type = ARPHRD_MCTP;
+ ndev->hard_header_len = sizeof(struct acpi_pcct_ext_pcc_shared_memory);
+ ndev->tx_queue_len = 0;
+ ndev->flags = IFF_NOARP;
+ ndev->netdev_ops = &mctp_pcc_netdev_ops;
+ ndev->needs_free_netdev = true;
+ ndev->pcpu_stat_type = NETDEV_PCPU_STAT_DSTATS;
+}
+
+struct mctp_pcc_lookup_context {
+ int index;
+ u32 inbox_index;
+ u32 outbox_index;
+};
+
+static acpi_status lookup_pcct_indices(struct acpi_resource *ares,
+ void *context)
+{
+ struct mctp_pcc_lookup_context *luc = context;
+ struct acpi_resource_address32 *addr;
+
+ if (ares->type != PCC_DWORD_TYPE)
+ return AE_OK;
+
+ addr = ACPI_CAST_PTR(struct acpi_resource_address32, &ares->data);
+ switch (luc->index) {
+ case 0:
+ luc->outbox_index = addr[0].address.minimum;
+ break;
+ case 1:
+ luc->inbox_index = addr[0].address.minimum;
+ break;
+ }
+ luc->index++;
+ return AE_OK;
+}
+
+static void mctp_cleanup_netdev(void *data)
+{
+ struct net_device *ndev = data;
+
+ mctp_unregister_netdev(ndev);
+}
+
+static int initialize_MTU(struct net_device *ndev)
+{
+ struct mctp_pcc_ndev *mctp_pcc_ndev;
+ struct mctp_pcc_mailbox *outbox;
+ struct pcc_mbox_chan *pchan;
+ int mctp_pcc_mtu;
+
+ mctp_pcc_mtu = MCTP_MIN_MTU;
+ mctp_pcc_ndev = netdev_priv(ndev);
+ outbox = &mctp_pcc_ndev->outbox;
+ pchan = pcc_mbox_request_channel(&outbox->client, outbox->index);
+ if (IS_ERR(pchan))
+ return -1;
+ if (pchan->shmem_size < MCTP_MIN_MTU)
+ return -1;
+
+ mctp_pcc_mtu = pchan->shmem_size;
+ pcc_mbox_free_channel(pchan);
+
+ mctp_pcc_mtu = mctp_pcc_mtu - sizeof(struct acpi_pcct_ext_pcc_shared_memory);
+ ndev->mtu = MCTP_MIN_MTU;
+ ndev->max_mtu = mctp_pcc_mtu;
+ ndev->min_mtu = MCTP_MIN_MTU;
+
+ return 0;
+}
+
+static int mctp_pcc_driver_add(struct acpi_device *acpi_dev)
+{
+ struct mctp_pcc_lookup_context context = {0};
+ struct mctp_pcc_ndev *mctp_pcc_ndev;
+ struct device *dev = &acpi_dev->dev;
+ struct net_device *ndev;
+ acpi_handle dev_handle;
+ acpi_status status;
+ char name[32];
+ int rc;
+
+ dev_dbg(dev, "Adding mctp_pcc device for HID %s\n",
+ acpi_device_hid(acpi_dev));
+ dev_handle = acpi_device_handle(acpi_dev);
+ status = acpi_walk_resources(dev_handle, "_CRS", lookup_pcct_indices,
+ &context);
+ if (!ACPI_SUCCESS(status)) {
+ dev_err(dev, "FAILED to lookup PCC indexes from CRS\n");
+ return -EINVAL;
+ }
+
+ snprintf(name, sizeof(name), "mctppcc%d", context.inbox_index);
+ ndev = alloc_netdev(sizeof(*mctp_pcc_ndev), name, NET_NAME_PREDICTABLE,
+ mctp_pcc_setup);
+ if (!ndev)
+ return -ENOMEM;
+
+ mctp_pcc_ndev = netdev_priv(ndev);
+
+ mctp_pcc_ndev->inbox.index = context.inbox_index;
+ mctp_pcc_ndev->inbox.client.dev = dev;
+ mctp_pcc_ndev->outbox.index = context.outbox_index;
+ mctp_pcc_ndev->outbox.client.dev = dev;
+
+ mctp_pcc_ndev->outbox.client.tx_prepare = mctp_pcc_tx_prepare;
+ mctp_pcc_ndev->outbox.client.tx_done = mctp_pcc_tx_done;
+ mctp_pcc_ndev->acpi_device = acpi_dev;
+ mctp_pcc_ndev->ndev = ndev;
+ acpi_dev->driver_data = mctp_pcc_ndev;
+
+ rc = initialize_MTU(ndev);
+ if (rc)
+ goto free_netdev;
+
+ rc = mctp_register_netdev(ndev, NULL, MCTP_PHYS_BINDING_PCC);
+ if (rc)
+ goto free_netdev;
+
+ return devm_add_action_or_reset(dev, mctp_cleanup_netdev, ndev);
+free_netdev:
+ free_netdev(ndev);
+ return rc;
+}
+
+static const struct acpi_device_id mctp_pcc_device_ids[] = {
+ { "DMT0001" },
+ {}
+};
+
+static struct acpi_driver mctp_pcc_driver = {
+ .name = "mctp_pcc",
+ .class = "Unknown",
+ .ids = mctp_pcc_device_ids,
+ .ops = {
+ .add = mctp_pcc_driver_add,
+ },
+};
+
+module_acpi_driver(mctp_pcc_driver);
+
+MODULE_DEVICE_TABLE(acpi, mctp_pcc_device_ids);
+
+MODULE_DESCRIPTION("MCTP PCC ACPI device");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Adam Young <admiyo@os.amperecomputing.com>");
--
2.43.0
^ permalink raw reply related
* Re: [PATCH v4 net-next 5/5] net: pcs: pcs-mtk-lynxi: deprecate "mediatek,pnswap"
From: Frank Wunderlich @ 2026-03-30 17:52 UTC (permalink / raw)
To: Vladimir Oltean
Cc: netdev, devicetree, linux-kernel, linux-mediatek, Daniel Golle,
Horatiu Vultur, Bj√∏rn Mork, Andrew Lunn,
Heiner Kallweit, Russell King, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Matthias Brugger, AngeloGioacchino Del Regno,
Eric Woudstra, Alexander Couzens, Chester A. Unal, DENG Qingfang,
Sean Wang, Felix Fietkau
In-Reply-To: <20260326215404.krh6v3mmnqdlndli@skbuf>
Hi Vladimir
Thanks for the patch and sorry for my delay...i was away this weekend so i was not able to test.
traffic works again (but there is only read now) and this is the result of your debug prints:
root@bpi-r3:~# dmesg | grep SGMSYS_QPHY_WRAP_CTRL
[ 2.706963] SGMSYS_QPHY_WRAP_CTRL = 0x501, intending to write 0x500
[ 9.134081] SGMSYS_QPHY_WRAP_CTRL = 0x500, intending to write 0x500
R3/mt7986 has 2 MAC, and switch is on the first, so value will change, not sure why this is different.
i have not found SGMSYS_QPHY_WRAP_CTRL or something related with polarity in ethernet/mac-
(drivers/net/ethernet/mediatek/mtk_eth_soc.c) or switch-driver (drivers/net/dsa/mt7530{,-mdio}.c)
in case they manipulate this register too (of course they should not). Also looked into the pcs-handling
in both drivers, but see nothing related to polarity. And looked for possible duplicate register const
definition (other name for 0xec).
regards Frank
Am 26. März 2026 um 22:54 schrieb "Vladimir Oltean" <vladimir.oltean@nxp.com>:
>
> Hi Frank,
>
> On Tue, Mar 24, 2026 at 06:36:44AM +0000, Frank Wunderlich wrote:
>
> >
> > Hi,
> >
> > looks like this patch breaks BPI-R3 serdes between mt7986 SoC and mt7531 switch in 7.0 (6.19 is ok).
> > in ethtool i see only tx on mac but no rx. if i revert this patch i can ping through dsa-ports again.
> >
> > i did not completely understanding the code with the default-pol as it is now splitted between rx and tx.
> >
> > mt7986 and this board does not have mediatek,pnswap set, so the final regmap_update_bits writes val=0,
> > before there was only write to this register on invert mode...but i guess this should not break. Maybe some
> > kind of timing issue between mac and switch?
> >
> > maybe reverting this patch skips changes made here:
> > bde1ae2d52ab 2026-01-19 net: pcs: pcs-mtk-lynxi: pass SGMIISYS OF node to PCS
> >
> > I resend as last try was sending as html (option "always send as text" in webmailer seems to be ignored
> > somehow, had to choose "unformatted" in this response too).
> >
> > regards Frank
> >
> Sorry for the delay.
>
> If writing val=0 breaks the link, I'm curious
> (a) whether it still breaks if we don't write anything at all
> (b) what was the register value originally
>
> Could you please test the patch below and let me know what it prints,
> and whether traffic passes with it applied?
>
> -- >8 --
> diff --git a/drivers/net/pcs/pcs-mtk-lynxi.c b/drivers/net/pcs/pcs-mtk-lynxi.c
> index c12f8087af9b..5c5f45b93b82 100644
> --- a/drivers/net/pcs/pcs-mtk-lynxi.c
> +++ b/drivers/net/pcs/pcs-mtk-lynxi.c
> @@ -126,7 +126,7 @@ static int mtk_pcs_config_polarity(struct mtk_pcs_lynxi *mpcs,
> {
> struct fwnode_handle *fwnode = mpcs->fwnode, *pcs_fwnode;
> unsigned int pol, default_pol = PHY_POL_NORMAL;
> - unsigned int val = 0;
> + unsigned int val = 0, tmp;
> int ret;
>
> if (fwnode_property_read_bool(fwnode, "mediatek,pnswap"))
> @@ -153,8 +153,14 @@ static int mtk_pcs_config_polarity(struct mtk_pcs_lynxi *mpcs,
> if (pol == PHY_POL_INVERT)
> val |= SGMII_PN_SWAP_TX;
>
> - return regmap_update_bits(mpcs->regmap, SGMSYS_QPHY_WRAP_CTRL,
> - SGMII_PN_SWAP_RX | SGMII_PN_SWAP_TX, val);
> + ret = regmap_read(mpcs->regmap, SGMSYS_QPHY_WRAP_CTRL, &tmp);
> + if (ret)
> + return ret;
> +
> + pr_err("SGMSYS_QPHY_WRAP_CTRL = 0x%x, intending to write 0x%lx\n",
> + tmp, (tmp & ~(SGMII_PN_SWAP_RX | SGMII_PN_SWAP_TX)) | val);
> +
> + return 0;
> }
>
> static int mtk_pcs_lynxi_config(struct phylink_pcs *pcs, unsigned int neg_mode,
> -- >8 --
>
regards Frank
^ permalink raw reply
* Re: [PATCH net-next 3/3] net: usb: smsc95xx: suspend PHY during USB suspend
From: Oliver Neukum @ 2026-03-30 17:39 UTC (permalink / raw)
To: Parthiban Veerasooran, piergiorgio.beruto, andrew, hkallweit1,
linux, davem, edumazet, kuba, pabeni, steve.glendinning,
UNGLinuxDriver
Cc: netdev, linux-usb
In-Reply-To: <20260330134222.67597-4-parthiban.veerasooran@microchip.com>
On 30.03.26 15:42, Parthiban Veerasooran wrote:
> The smsc95xx driver registers a PHY device but does not currently
> propagate suspend events to it when the USB interface is suspended.
>
> Call phy_suspend() from the driver's suspend callback so the attached
> PHY can properly enter low-power state during system or runtime
> suspend. This aligns smsc95xx suspend handling with other network
> drivers that manage an external or integrated PHY.
>
> Without this, the PHY may remain active and fail to execute its own
> suspend procedure, leading to unnecessary power consumption or
> incorrect resume behavior.
>
> This change is also required for the EVB-LAN8670-USB Rev.D0 device to
> support OATC10-compliant sleep and wake functionality.
>
> Signed-off-by: Parthiban Veerasooran <parthiban.veerasooran@microchip.com>
> ---
> drivers/net/usb/smsc95xx.c | 6 ++++++
> 1 file changed, 6 insertions(+)
>
> diff --git a/drivers/net/usb/smsc95xx.c b/drivers/net/usb/smsc95xx.c
> index 42e4048b574b..3a6e03b7410a 100644
> --- a/drivers/net/usb/smsc95xx.c
> +++ b/drivers/net/usb/smsc95xx.c
> @@ -1550,6 +1550,12 @@ static int smsc95xx_suspend(struct usb_interface *intf, pm_message_t message)
>
> pdata->pm_task = current;
>
> + if (pdata->phydev) {
> + ret = phy_suspend(pdata->phydev);
> + if (ret)
> + return ret;
> + }
At this point you have suspended the phy.
Hence the device can no longer transmit
> +
> ret = usbnet_suspend(intf, message);
This wants to
1. drain the queue if you do runtime PM
2. can return -EBUSY
> if (ret < 0) {
> netdev_warn(dev->net, "usbnet_suspend error\n");
And here it will return in the error case. With the phy
already suspended.
And, as a question of principle: Why do you suspend the phy
in suspend(), but take no action in resume()?
Regards
Oliver
^ permalink raw reply
* [PATCH v2 iproute2-next 4/4] rdma: Add FRMR pools set pinned command
From: Chiara Meiohas @ 2026-03-30 17:31 UTC (permalink / raw)
To: leon, dsahern, stephen
Cc: michaelgur, jgg, linux-rdma, netdev, Patrisious Haddad,
Chiara Meiohas
In-Reply-To: <20260330173118.766885-1-cmeiohas@nvidia.com>
From: Michael Guralnik <michaelgur@nvidia.com>
Add an option to set the amount of pinned handles to FRMR pool.
Pinned handles are not affected by aging and stay available for reuse in
the FRMR pool.
The pool is identified by a colon-separated key of hexadecimal fields
(vendor_key:num_dma_blocks:access_flags:ats) as shown in the 'show'
command output.
Usage:
Set 250 pinned handles to FRMR pool with key 0:800:0:0 on
device rocep8s0f0
$rdma resource set frmr_pools dev rocep8s0f0 pinned 0:800:0:0 250
Signed-off-by: Michael Guralnik <michaelgur@nvidia.com>
Reviewed-by: Patrisious Haddad <phaddad@nvidia.com>
Reviewed-by: Chiara Meiohas <cmeiohas@nvidia.com>
---
man/man8/rdma-resource.8 | 21 +++++++
rdma/res-frmr-pools.c | 121 ++++++++++++++++++++++++++++++++++++++-
rdma/res.c | 1 +
rdma/res.h | 1 +
4 files changed, 143 insertions(+), 1 deletion(-)
diff --git a/man/man8/rdma-resource.8 b/man/man8/rdma-resource.8
index a6dc33f3..1138cd23 100644
--- a/man/man8/rdma-resource.8
+++ b/man/man8/rdma-resource.8
@@ -33,6 +33,14 @@ rdma-resource \- rdma resource configuration
.BR aging
.IR AGING_PERIOD
+.ti -8
+.B rdma resource set frmr_pools
+.BR dev
+.IR DEV
+.BR pinned
+.IR POOL_KEY
+.IR PINNED_VALUE
+
.ti -8
.B rdma resource help
@@ -54,6 +62,14 @@ If this argument is omitted all links are listed.
.I "AGING_PERIOD"
- specifies the aging period in seconds for unused FRMR handles. Handles unused for this period will be freed.
+.PP
+.I "POOL_KEY"
+- specifies the pool key that identifies a specific FRMR pool. The key is a colon-separated list of hexadecimal fields in the format vendor_key:num_dma_blocks:access_flags:ats.
+
+.PP
+.I "PINNED_VALUE"
+- specifies the pinned value for the FRMR pool. A non-zero value pins handles to the pool, preventing them from being freed by the aging mechanism.
+
.SH "EXAMPLES"
.PP
rdma resource show
@@ -141,6 +157,11 @@ rdma resource set frmr_pools dev rocep8s0f0 aging 120
Set the aging period for FRMR pools on device rocep8s0f0 to 120 seconds.
.RE
.PP
+rdma resource set frmr_pools dev rocep8s0f0 pinned 0:1000:0:0 25000
+.RS 4
+Pin 25000 handles to the FRMR pool identified by key 0:1000:0:0 on device rocep8s0f0 to prevent them from being freed.
+.RE
+.PP
.SH SEE ALSO
.BR rdma (8),
diff --git a/rdma/res-frmr-pools.c b/rdma/res-frmr-pools.c
index c9d80c4b..abcd2188 100644
--- a/rdma/res-frmr-pools.c
+++ b/rdma/res-frmr-pools.c
@@ -17,14 +17,68 @@ struct frmr_pool_key {
/* vendor_key(16) + ':' + num_dma_blocks(16) + ':' + access_flags(8) + ':' + ats(1) + '\0' */
#define FRMR_POOL_KEY_MAX_LEN 45
+static int decode_pool_key(const char *str, struct frmr_pool_key *key)
+{
+ const char *p = str;
+ char *end;
+ int i = 0;
+
+ while (*p) {
+ uint64_t val;
+
+ errno = 0;
+ val = strtoull(p, &end, 16);
+ if (errno == ERANGE || end == p || (*end != ':' && *end != '\0')) {
+ pr_err("Invalid pool key: %s\n", str);
+ return -EINVAL;
+ }
+
+ switch (i) {
+ case 0:
+ key->vendor_key = val;
+ break;
+ case 1:
+ key->num_dma_blocks = val;
+ break;
+ case 2:
+ if (val > UINT32_MAX)
+ goto out_of_range;
+ key->access_flags = val;
+ break;
+ case 3:
+ if (val != 0 && val != 1)
+ goto out_of_range;
+ key->ats = val;
+ break;
+ default:
+ if (val) {
+ pr_err("Unsupported pool attributes passed in pool key\n");
+ return -EINVAL;
+ }
+ }
+ i++;
+ p = *end ? end + 1 : end;
+ }
+
+ if (i < 4) {
+ pr_err("Invalid pool key: %s, expected 4 fields\n", str);
+ return -EINVAL;
+ }
+ return 0;
+
+out_of_range:
+ pr_err("Pool key field at index %d value out of range\n", i);
+ return -EINVAL;
+}
+
static int res_frmr_pools_line(struct rd *rd, const char *name, int idx,
struct nlattr **nla_line)
{
uint64_t in_use = 0, max_in_use = 0, kernel_vendor_key = 0;
struct nlattr *key_tb[RDMA_NLDEV_ATTR_MAX] = {};
+ uint32_t queue_handles = 0, pinned_handles = 0;
char key_str[FRMR_POOL_KEY_MAX_LEN];
struct frmr_pool_key key = { 0 };
- uint32_t queue_handles = 0;
if (nla_line[RDMA_NLDEV_ATTR_RES_FRMR_POOL_KEY]) {
if (mnl_attr_parse_nested(
@@ -92,6 +146,13 @@ static int res_frmr_pools_line(struct rd *rd, const char *name, int idx,
nla_line[RDMA_NLDEV_ATTR_RES_FRMR_POOL_MAX_IN_USE]))
goto out;
+ if (nla_line[RDMA_NLDEV_ATTR_RES_FRMR_POOL_PINNED])
+ pinned_handles = mnl_attr_get_u32(
+ nla_line[RDMA_NLDEV_ATTR_RES_FRMR_POOL_PINNED]);
+ if (rd_is_filtered_attr(rd, "pinned", pinned_handles,
+ nla_line[RDMA_NLDEV_ATTR_RES_FRMR_POOL_PINNED]))
+ goto out;
+
open_json_object(NULL);
print_dev(idx, name);
@@ -127,6 +188,8 @@ static int res_frmr_pools_line(struct rd *rd, const char *name, int idx,
nla_line[RDMA_NLDEV_ATTR_RES_FRMR_POOL_IN_USE]);
res_print_u64("max_in_use", max_in_use,
nla_line[RDMA_NLDEV_ATTR_RES_FRMR_POOL_MAX_IN_USE]);
+ res_print_u32("pinned", pinned_handles,
+ nla_line[RDMA_NLDEV_ATTR_RES_FRMR_POOL_PINNED]);
print_driver_table(rd, nla_line[RDMA_NLDEV_ATTR_DRIVER]);
close_json_object();
@@ -202,10 +265,65 @@ static int res_frmr_pools_one_set_aging(struct rd *rd)
return rd_sendrecv_msg(rd, seq);
}
+static int res_frmr_pools_one_set_pinned(struct rd *rd)
+{
+ struct frmr_pool_key pool_key = { 0 };
+ struct nlattr *key_attr;
+ uint32_t pinned_value;
+ const char *key_str;
+ uint32_t seq;
+
+ if (rd_no_arg(rd)) {
+ pr_err("Please provide pool key and pinned value.\n");
+ return -EINVAL;
+ }
+
+ key_str = rd_argv(rd);
+ rd_arg_inc(rd);
+
+ if (decode_pool_key(key_str, &pool_key))
+ return -EINVAL;
+
+ if (rd_no_arg(rd)) {
+ pr_err("Please provide pinned value.\n");
+ return -EINVAL;
+ }
+
+ if (get_u32(&pinned_value, rd_argv(rd), 10)) {
+ pr_err("Invalid pinned value: %s\n", rd_argv(rd));
+ return -EINVAL;
+ }
+
+ rd_prepare_msg(rd, RDMA_NLDEV_CMD_RES_FRMR_POOLS_SET, &seq,
+ (NLM_F_REQUEST | NLM_F_ACK));
+ mnl_attr_put_u32(rd->nlh, RDMA_NLDEV_ATTR_DEV_INDEX, rd->dev_idx);
+
+ mnl_attr_put_u32(rd->nlh, RDMA_NLDEV_ATTR_RES_FRMR_POOL_PINNED,
+ pinned_value);
+
+ key_attr =
+ mnl_attr_nest_start(rd->nlh, RDMA_NLDEV_ATTR_RES_FRMR_POOL_KEY);
+ mnl_attr_put_u8(rd->nlh, RDMA_NLDEV_ATTR_RES_FRMR_POOL_KEY_ATS,
+ pool_key.ats);
+ mnl_attr_put_u32(rd->nlh,
+ RDMA_NLDEV_ATTR_RES_FRMR_POOL_KEY_ACCESS_FLAGS,
+ pool_key.access_flags);
+ mnl_attr_put_u64(rd->nlh, RDMA_NLDEV_ATTR_RES_FRMR_POOL_KEY_VENDOR_KEY,
+ pool_key.vendor_key);
+ mnl_attr_put_u64(rd->nlh,
+ RDMA_NLDEV_ATTR_RES_FRMR_POOL_KEY_NUM_DMA_BLOCKS,
+ pool_key.num_dma_blocks);
+ mnl_attr_nest_end(rd->nlh, key_attr);
+
+ return rd_sendrecv_msg(rd, seq);
+}
+
static int res_frmr_pools_one_set_help(struct rd *rd)
{
pr_out("Usage: %s set frmr_pools dev DEV aging AGING_PERIOD\n",
rd->filename);
+ pr_out("Usage: %s set frmr_pools dev DEV pinned POOL_KEY PINNED_VALUE\n",
+ rd->filename);
return 0;
}
@@ -215,6 +333,7 @@ static int res_frmr_pools_one_set(struct rd *rd)
{ NULL, res_frmr_pools_one_set_help },
{ "help", res_frmr_pools_one_set_help },
{ "aging", res_frmr_pools_one_set_aging },
+ { "pinned", res_frmr_pools_one_set_pinned },
{ 0 }
};
diff --git a/rdma/res.c b/rdma/res.c
index 63d8386a..062f0007 100644
--- a/rdma/res.c
+++ b/rdma/res.c
@@ -29,6 +29,7 @@ static int res_help(struct rd *rd)
pr_out(" resource show frmr_pools dev [DEV]\n");
pr_out(" resource show frmr_pools dev [DEV] [FILTER-NAME FILTER-VALUE]\n");
pr_out(" resource set frmr_pools dev DEV aging AGING_PERIOD\n");
+ pr_out(" resource set frmr_pools dev DEV pinned POOL_KEY PINNED_VALUE\n");
return 0;
}
diff --git a/rdma/res.h b/rdma/res.h
index dffbdb52..4758f2ea 100644
--- a/rdma/res.h
+++ b/rdma/res.h
@@ -198,6 +198,7 @@ struct filters frmr_pools_valid_filters[MAX_NUMBER_OF_FILTERS] = {
{ .name = "queue", .is_number = true },
{ .name = "in_use", .is_number = true },
{ .name = "max_in_use", .is_number = true },
+ { .name = "pinned", .is_number = true },
};
RES_FUNC(res_frmr_pools, RDMA_NLDEV_CMD_RES_FRMR_POOLS_GET,
--
2.38.1
^ permalink raw reply related
* [PATCH v2 iproute2-next 3/4] rdma: Add FRMR pools set aging command
From: Chiara Meiohas @ 2026-03-30 17:31 UTC (permalink / raw)
To: leon, dsahern, stephen
Cc: michaelgur, jgg, linux-rdma, netdev, Patrisious Haddad,
Chiara Meiohas
In-Reply-To: <20260330173118.766885-1-cmeiohas@nvidia.com>
From: Michael Guralnik <michaelgur@nvidia.com>
Add support for configuring the aging period of FRMR pools.
The aging mechanism frees unused FRMR handles that have not been
in use for the specified period.
Usage:
rdma resource set frmr_pools dev DEV aging AGING_PERIOD
Signed-off-by: Michael Guralnik <michaelgur@nvidia.com>
Reviewed-by: Patrisious Haddad <phaddad@nvidia.com>
Reviewed-by: Chiara Meiohas <cmeiohas@nvidia.com>
---
man/man8/rdma-resource.8 | 22 +++++++++++++++
rdma/res-frmr-pools.c | 59 ++++++++++++++++++++++++++++++++++++++++
rdma/res.c | 13 +++++++++
rdma/res.h | 1 +
4 files changed, 95 insertions(+)
diff --git a/man/man8/rdma-resource.8 b/man/man8/rdma-resource.8
index 4e2ba39a..a6dc33f3 100644
--- a/man/man8/rdma-resource.8
+++ b/man/man8/rdma-resource.8
@@ -26,6 +26,13 @@ rdma-resource \- rdma resource configuration
.B rdma resource show
.RI "[ " DEV/PORT_INDEX " ]"
+.ti -8
+.B rdma resource set frmr_pools
+.BR dev
+.IR DEV
+.BR aging
+.IR AGING_PERIOD
+
.ti -8
.B rdma resource help
@@ -37,6 +44,16 @@ rdma-resource \- rdma resource configuration
- specifies the RDMA link to show.
If this argument is omitted all links are listed.
+.SS rdma resource set - configure resource related parameters
+
+.PP
+.I "DEV"
+- specifies the RDMA device to configure.
+
+.PP
+.I "AGING_PERIOD"
+- specifies the aging period in seconds for unused FRMR handles. Handles unused for this period will be freed.
+
.SH "EXAMPLES"
.PP
rdma resource show
@@ -119,6 +136,11 @@ rdma resource show frmr_pools ats 1
Show FRMR pools that have ats attribute set.
.RE
.PP
+rdma resource set frmr_pools dev rocep8s0f0 aging 120
+.RS 4
+Set the aging period for FRMR pools on device rocep8s0f0 to 120 seconds.
+.RE
+.PP
.SH SEE ALSO
.BR rdma (8),
diff --git a/rdma/res-frmr-pools.c b/rdma/res-frmr-pools.c
index 7d99a728..c9d80c4b 100644
--- a/rdma/res-frmr-pools.c
+++ b/rdma/res-frmr-pools.c
@@ -172,3 +172,62 @@ int res_frmr_pools_parse_cb(const struct nlmsghdr *nlh, void *data)
}
return ret;
}
+
+static int res_frmr_pools_one_set_aging(struct rd *rd)
+{
+ uint32_t aging_period;
+ uint32_t seq;
+
+ if (rd_no_arg(rd)) {
+ pr_err("Please provide aging period value.\n");
+ return -EINVAL;
+ }
+
+ if (get_u32(&aging_period, rd_argv(rd), 10)) {
+ pr_err("Invalid aging period value: %s\n", rd_argv(rd));
+ return -EINVAL;
+ }
+
+ if (aging_period == 0) {
+ pr_err("Setting the aging period to zero is not supported.\n");
+ return -EINVAL;
+ }
+
+ rd_prepare_msg(rd, RDMA_NLDEV_CMD_RES_FRMR_POOLS_SET, &seq,
+ (NLM_F_REQUEST | NLM_F_ACK));
+ mnl_attr_put_u32(rd->nlh, RDMA_NLDEV_ATTR_DEV_INDEX, rd->dev_idx);
+ mnl_attr_put_u32(rd->nlh, RDMA_NLDEV_ATTR_RES_FRMR_POOL_AGING_PERIOD,
+ aging_period);
+
+ return rd_sendrecv_msg(rd, seq);
+}
+
+static int res_frmr_pools_one_set_help(struct rd *rd)
+{
+ pr_out("Usage: %s set frmr_pools dev DEV aging AGING_PERIOD\n",
+ rd->filename);
+ return 0;
+}
+
+static int res_frmr_pools_one_set(struct rd *rd)
+{
+ const struct rd_cmd cmds[] = {
+ { NULL, res_frmr_pools_one_set_help },
+ { "help", res_frmr_pools_one_set_help },
+ { "aging", res_frmr_pools_one_set_aging },
+ { 0 }
+ };
+
+ return rd_exec_cmd(rd, cmds, "resource set frmr_pools command");
+}
+
+int res_frmr_pools_set(struct rd *rd)
+{
+ int ret;
+
+ ret = rd_set_arg_to_devname(rd);
+ if (ret)
+ return ret;
+
+ return rd_exec_require_dev(rd, res_frmr_pools_one_set);
+}
diff --git a/rdma/res.c b/rdma/res.c
index f1f13d74..63d8386a 100644
--- a/rdma/res.c
+++ b/rdma/res.c
@@ -28,6 +28,7 @@ static int res_help(struct rd *rd)
pr_out(" resource show srq dev [DEV] [FILTER-NAME FILTER-VALUE]\n");
pr_out(" resource show frmr_pools dev [DEV]\n");
pr_out(" resource show frmr_pools dev [DEV] [FILTER-NAME FILTER-VALUE]\n");
+ pr_out(" resource set frmr_pools dev DEV aging AGING_PERIOD\n");
return 0;
}
@@ -252,11 +253,23 @@ static int res_show(struct rd *rd)
return rd_exec_cmd(rd, cmds, "parameter");
}
+static int res_set(struct rd *rd)
+{
+ const struct rd_cmd cmds[] = {
+ { NULL, res_help },
+ { "frmr_pools", res_frmr_pools_set },
+ { 0 }
+ };
+
+ return rd_exec_cmd(rd, cmds, "resource set command");
+}
+
int cmd_res(struct rd *rd)
{
const struct rd_cmd cmds[] = {
{ NULL, res_show },
{ "show", res_show },
+ { "set", res_set },
{ "list", res_show },
{ "help", res_help },
{ 0 }
diff --git a/rdma/res.h b/rdma/res.h
index 30edb8f8..dffbdb52 100644
--- a/rdma/res.h
+++ b/rdma/res.h
@@ -203,6 +203,7 @@ struct filters frmr_pools_valid_filters[MAX_NUMBER_OF_FILTERS] = {
RES_FUNC(res_frmr_pools, RDMA_NLDEV_CMD_RES_FRMR_POOLS_GET,
frmr_pools_valid_filters, true, 0);
+int res_frmr_pools_set(struct rd *rd);
void print_dev(uint32_t idx, const char *name);
void print_link(uint32_t idx, const char *name, uint32_t port, struct nlattr **nla_line);
void print_key(const char *name, uint64_t val, struct nlattr *nlattr);
--
2.38.1
^ permalink raw reply related
* [PATCH v2 iproute2-next 2/4] rdma: Add resource FRMR pools show command
From: Chiara Meiohas @ 2026-03-30 17:31 UTC (permalink / raw)
To: leon, dsahern, stephen
Cc: michaelgur, jgg, linux-rdma, netdev, Patrisious Haddad,
Chiara Meiohas
In-Reply-To: <20260330173118.766885-1-cmeiohas@nvidia.com>
From: Michael Guralnik <michaelgur@nvidia.com>
Allow users to see the FRMR pools that were created on the devices,
their properties and their usage statistics.
The set of properties of each pool are encoded as a colon-separated
list of hexadecimal fields (vendor_key:num_dma_blocks:access_flags:ats)
to simplify referencing a specific pool in 'set' commands.
Sample output:
$rdma resource show frmr_pools
dev rocep8s0f0 key 0:1000:0:0 queue 0 in_use 0 max_in_use 200
dev rocep8s0f0 key 0:800:0:0 queue 0 in_use 0 max_in_use 200
dev rocep8s0f0 key 0:400:0:0 queue 0 in_use 0 max_in_use 200
$rdma resource show frmr_pools -d
dev rocep8s0f0 key 0:1000:0:0 ats 0 access_flags 0 vendor_key 0 num_dma_blocks 4096 queue 0 in_use 0 max_in_use 200
dev rocep8s0f0 key 0:800:0:0 ats 0 access_flags 0 vendor_key 0 num_dma_blocks 2048 queue 0 in_use 0 max_in_use 200
dev rocep8s0f0 key 0:400:0:0 ats 0 access_flags 0 vendor_key 0 num_dma_blocks 1024 queue 0 in_use 0 max_in_use 200
$rdma resource show frmr_pools num_dma_blocks 2048
dev rocep8s0f0 key 0:800:0:0 queue 0 in_use 0 max_in_use 200
Signed-off-by: Michael Guralnik <michaelgur@nvidia.com>
Reviewed-by: Patrisious Haddad <phaddad@nvidia.com>
Reviewed-by: Chiara Meiohas <cmeiohas@nvidia.com>
---
man/man8/rdma-resource.8 | 8 +-
rdma/Makefile | 2 +-
rdma/res-frmr-pools.c | 174 +++++++++++++++++++++++++++++++++++++++
rdma/res.c | 5 +-
rdma/res.h | 18 ++++
5 files changed, 204 insertions(+), 3 deletions(-)
create mode 100644 rdma/res-frmr-pools.c
diff --git a/man/man8/rdma-resource.8 b/man/man8/rdma-resource.8
index 61bec471..4e2ba39a 100644
--- a/man/man8/rdma-resource.8
+++ b/man/man8/rdma-resource.8
@@ -13,7 +13,8 @@ rdma-resource \- rdma resource configuration
.ti -8
.IR RESOURCE " := { "
-.BR cm_id " | " cq " | " mr " | " pd " | " qp " | " ctx " | " srq " }"
+.BR cm_id " | " cq " | " mr " | " pd " | " qp " | " ctx " | " srq " | "
+.BR frmr_pools " }"
.sp
.ti -8
@@ -113,6 +114,11 @@ rdma resource show srq lqpn 5-7
Show SRQs that the QPs with lqpn 5-7 are associated with.
.RE
.PP
+rdma resource show frmr_pools ats 1
+.RS
+Show FRMR pools that have ats attribute set.
+.RE
+.PP
.SH SEE ALSO
.BR rdma (8),
diff --git a/rdma/Makefile b/rdma/Makefile
index ed3c1c1c..66fe53f9 100644
--- a/rdma/Makefile
+++ b/rdma/Makefile
@@ -5,7 +5,7 @@ CFLAGS += -I./include/uapi/
RDMA_OBJ = rdma.o utils.o dev.o link.o res.o res-pd.o res-mr.o res-cq.o \
res-cmid.o res-qp.o sys.o stat.o stat-mr.o res-ctx.o res-srq.o \
- monitor.o
+ monitor.o res-frmr-pools.o
TARGETS += rdma
diff --git a/rdma/res-frmr-pools.c b/rdma/res-frmr-pools.c
new file mode 100644
index 00000000..7d99a728
--- /dev/null
+++ b/rdma/res-frmr-pools.c
@@ -0,0 +1,174 @@
+// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
+/*
+ * res-frmr-pools.c RDMA tool
+ * Authors: Michael Guralnik <michaelgur@nvidia.com>
+ */
+
+#include "res.h"
+#include <inttypes.h>
+
+struct frmr_pool_key {
+ uint64_t vendor_key;
+ uint64_t num_dma_blocks;
+ uint32_t access_flags;
+ uint8_t ats;
+};
+
+/* vendor_key(16) + ':' + num_dma_blocks(16) + ':' + access_flags(8) + ':' + ats(1) + '\0' */
+#define FRMR_POOL_KEY_MAX_LEN 45
+
+static int res_frmr_pools_line(struct rd *rd, const char *name, int idx,
+ struct nlattr **nla_line)
+{
+ uint64_t in_use = 0, max_in_use = 0, kernel_vendor_key = 0;
+ struct nlattr *key_tb[RDMA_NLDEV_ATTR_MAX] = {};
+ char key_str[FRMR_POOL_KEY_MAX_LEN];
+ struct frmr_pool_key key = { 0 };
+ uint32_t queue_handles = 0;
+
+ if (nla_line[RDMA_NLDEV_ATTR_RES_FRMR_POOL_KEY]) {
+ if (mnl_attr_parse_nested(
+ nla_line[RDMA_NLDEV_ATTR_RES_FRMR_POOL_KEY],
+ rd_attr_cb, key_tb) != MNL_CB_OK)
+ return MNL_CB_ERROR;
+
+ if (key_tb[RDMA_NLDEV_ATTR_RES_FRMR_POOL_KEY_ATS])
+ key.ats = mnl_attr_get_u8(
+ key_tb[RDMA_NLDEV_ATTR_RES_FRMR_POOL_KEY_ATS]);
+ if (key_tb[RDMA_NLDEV_ATTR_RES_FRMR_POOL_KEY_ACCESS_FLAGS])
+ key.access_flags = mnl_attr_get_u32(
+ key_tb[RDMA_NLDEV_ATTR_RES_FRMR_POOL_KEY_ACCESS_FLAGS]);
+ if (key_tb[RDMA_NLDEV_ATTR_RES_FRMR_POOL_KEY_VENDOR_KEY])
+ key.vendor_key = mnl_attr_get_u64(
+ key_tb[RDMA_NLDEV_ATTR_RES_FRMR_POOL_KEY_VENDOR_KEY]);
+ if (key_tb[RDMA_NLDEV_ATTR_RES_FRMR_POOL_KEY_NUM_DMA_BLOCKS])
+ key.num_dma_blocks = mnl_attr_get_u64(
+ key_tb[RDMA_NLDEV_ATTR_RES_FRMR_POOL_KEY_NUM_DMA_BLOCKS]);
+ if (key_tb[RDMA_NLDEV_ATTR_RES_FRMR_POOL_KEY_KERNEL_VENDOR_KEY])
+ kernel_vendor_key = mnl_attr_get_u64(
+ key_tb[RDMA_NLDEV_ATTR_RES_FRMR_POOL_KEY_KERNEL_VENDOR_KEY]);
+
+ if (rd_is_filtered_attr(
+ rd, "ats", key.ats,
+ key_tb[RDMA_NLDEV_ATTR_RES_FRMR_POOL_KEY_ATS]))
+ goto out;
+
+ if (rd_is_filtered_attr(
+ rd, "access_flags", key.access_flags,
+ key_tb[RDMA_NLDEV_ATTR_RES_FRMR_POOL_KEY_ACCESS_FLAGS]))
+ goto out;
+
+ if (rd_is_filtered_attr(
+ rd, "vendor_key", key.vendor_key,
+ key_tb[RDMA_NLDEV_ATTR_RES_FRMR_POOL_KEY_VENDOR_KEY]))
+ goto out;
+
+ if (rd_is_filtered_attr(
+ rd, "num_dma_blocks", key.num_dma_blocks,
+ key_tb[RDMA_NLDEV_ATTR_RES_FRMR_POOL_KEY_NUM_DMA_BLOCKS]))
+ goto out;
+ }
+
+ if (nla_line[RDMA_NLDEV_ATTR_RES_FRMR_POOL_QUEUE_HANDLES])
+ queue_handles = mnl_attr_get_u32(
+ nla_line[RDMA_NLDEV_ATTR_RES_FRMR_POOL_QUEUE_HANDLES]);
+ if (rd_is_filtered_attr(
+ rd, "queue", queue_handles,
+ nla_line[RDMA_NLDEV_ATTR_RES_FRMR_POOL_QUEUE_HANDLES]))
+ goto out;
+
+ if (nla_line[RDMA_NLDEV_ATTR_RES_FRMR_POOL_IN_USE])
+ in_use = mnl_attr_get_u64(
+ nla_line[RDMA_NLDEV_ATTR_RES_FRMR_POOL_IN_USE]);
+ if (rd_is_filtered_attr(rd, "in_use", in_use,
+ nla_line[RDMA_NLDEV_ATTR_RES_FRMR_POOL_IN_USE]))
+ goto out;
+
+ if (nla_line[RDMA_NLDEV_ATTR_RES_FRMR_POOL_MAX_IN_USE])
+ max_in_use = mnl_attr_get_u64(
+ nla_line[RDMA_NLDEV_ATTR_RES_FRMR_POOL_MAX_IN_USE]);
+ if (rd_is_filtered_attr(
+ rd, "max_in_use", max_in_use,
+ nla_line[RDMA_NLDEV_ATTR_RES_FRMR_POOL_MAX_IN_USE]))
+ goto out;
+
+ open_json_object(NULL);
+ print_dev(idx, name);
+
+ if (nla_line[RDMA_NLDEV_ATTR_RES_FRMR_POOL_KEY]) {
+ snprintf(key_str, sizeof(key_str),
+ "%" PRIx64 ":%" PRIx64 ":%x:%s",
+ key.vendor_key, key.num_dma_blocks,
+ key.access_flags, key.ats ? "1" : "0");
+ print_string(PRINT_ANY, "key", "key %s ", key_str);
+
+ if (rd->show_details) {
+ res_print_u32(
+ "ats", key.ats,
+ key_tb[RDMA_NLDEV_ATTR_RES_FRMR_POOL_KEY_ATS]);
+ res_print_u32(
+ "access_flags", key.access_flags,
+ key_tb[RDMA_NLDEV_ATTR_RES_FRMR_POOL_KEY_ACCESS_FLAGS]);
+ res_print_u64(
+ "vendor_key", key.vendor_key,
+ key_tb[RDMA_NLDEV_ATTR_RES_FRMR_POOL_KEY_VENDOR_KEY]);
+ res_print_u64(
+ "num_dma_blocks", key.num_dma_blocks,
+ key_tb[RDMA_NLDEV_ATTR_RES_FRMR_POOL_KEY_NUM_DMA_BLOCKS]);
+ res_print_u64(
+ "kernel_vendor_key", kernel_vendor_key,
+ key_tb[RDMA_NLDEV_ATTR_RES_FRMR_POOL_KEY_KERNEL_VENDOR_KEY]);
+ }
+ }
+
+ res_print_u32("queue", queue_handles,
+ nla_line[RDMA_NLDEV_ATTR_RES_FRMR_POOL_QUEUE_HANDLES]);
+ res_print_u64("in_use", in_use,
+ nla_line[RDMA_NLDEV_ATTR_RES_FRMR_POOL_IN_USE]);
+ res_print_u64("max_in_use", max_in_use,
+ nla_line[RDMA_NLDEV_ATTR_RES_FRMR_POOL_MAX_IN_USE]);
+
+ print_driver_table(rd, nla_line[RDMA_NLDEV_ATTR_DRIVER]);
+ close_json_object();
+ newline();
+
+out:
+ return MNL_CB_OK;
+}
+
+int res_frmr_pools_idx_parse_cb(const struct nlmsghdr *nlh, void *data)
+{
+ return MNL_CB_OK;
+}
+
+int res_frmr_pools_parse_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct nlattr *tb[RDMA_NLDEV_ATTR_MAX] = {};
+ struct nlattr *nla_table, *nla_entry;
+ struct rd *rd = data;
+ int ret = MNL_CB_OK;
+ const char *name;
+ uint32_t idx;
+
+ mnl_attr_parse(nlh, 0, rd_attr_cb, tb);
+ if (!tb[RDMA_NLDEV_ATTR_DEV_INDEX] || !tb[RDMA_NLDEV_ATTR_DEV_NAME] ||
+ !tb[RDMA_NLDEV_ATTR_RES_FRMR_POOLS])
+ return MNL_CB_ERROR;
+
+ name = mnl_attr_get_str(tb[RDMA_NLDEV_ATTR_DEV_NAME]);
+ idx = mnl_attr_get_u32(tb[RDMA_NLDEV_ATTR_DEV_INDEX]);
+ nla_table = tb[RDMA_NLDEV_ATTR_RES_FRMR_POOLS];
+
+ mnl_attr_for_each_nested(nla_entry, nla_table) {
+ struct nlattr *nla_line[RDMA_NLDEV_ATTR_MAX] = {};
+
+ ret = mnl_attr_parse_nested(nla_entry, rd_attr_cb, nla_line);
+ if (ret != MNL_CB_OK)
+ break;
+
+ ret = res_frmr_pools_line(rd, name, idx, nla_line);
+ if (ret != MNL_CB_OK)
+ break;
+ }
+ return ret;
+}
diff --git a/rdma/res.c b/rdma/res.c
index 7e7de042..f1f13d74 100644
--- a/rdma/res.c
+++ b/rdma/res.c
@@ -11,7 +11,7 @@ static int res_help(struct rd *rd)
{
pr_out("Usage: %s resource\n", rd->filename);
pr_out(" resource show [DEV]\n");
- pr_out(" resource show [qp|cm_id|pd|mr|cq|ctx|srq]\n");
+ pr_out(" resource show [qp|cm_id|pd|mr|cq|ctx|srq|frmr_pools]\n");
pr_out(" resource show qp link [DEV/PORT]\n");
pr_out(" resource show qp link [DEV/PORT] [FILTER-NAME FILTER-VALUE]\n");
pr_out(" resource show cm_id link [DEV/PORT]\n");
@@ -26,6 +26,8 @@ static int res_help(struct rd *rd)
pr_out(" resource show ctx dev [DEV] [FILTER-NAME FILTER-VALUE]\n");
pr_out(" resource show srq dev [DEV]\n");
pr_out(" resource show srq dev [DEV] [FILTER-NAME FILTER-VALUE]\n");
+ pr_out(" resource show frmr_pools dev [DEV]\n");
+ pr_out(" resource show frmr_pools dev [DEV] [FILTER-NAME FILTER-VALUE]\n");
return 0;
}
@@ -237,6 +239,7 @@ static int res_show(struct rd *rd)
{ "pd", res_pd },
{ "ctx", res_ctx },
{ "srq", res_srq },
+ { "frmr_pools", res_frmr_pools },
{ 0 }
};
diff --git a/rdma/res.h b/rdma/res.h
index fd09ce7d..30edb8f8 100644
--- a/rdma/res.h
+++ b/rdma/res.h
@@ -26,6 +26,8 @@ int res_ctx_parse_cb(const struct nlmsghdr *nlh, void *data);
int res_ctx_idx_parse_cb(const struct nlmsghdr *nlh, void *data);
int res_srq_parse_cb(const struct nlmsghdr *nlh, void *data);
int res_srq_idx_parse_cb(const struct nlmsghdr *nlh, void *data);
+int res_frmr_pools_parse_cb(const struct nlmsghdr *nlh, void *data);
+int res_frmr_pools_idx_parse_cb(const struct nlmsghdr *nlh, void *data);
static inline uint32_t res_get_command(uint32_t command, struct rd *rd)
{
@@ -185,6 +187,22 @@ struct filters srq_valid_filters[MAX_NUMBER_OF_FILTERS] = {
RES_FUNC(res_srq, RDMA_NLDEV_CMD_RES_SRQ_GET, srq_valid_filters, true,
RDMA_NLDEV_ATTR_RES_SRQN);
+
+static const
+struct filters frmr_pools_valid_filters[MAX_NUMBER_OF_FILTERS] = {
+ { .name = "dev", .is_number = false },
+ { .name = "ats", .is_number = true },
+ { .name = "access_flags", .is_number = true },
+ { .name = "vendor_key", .is_number = true },
+ { .name = "num_dma_blocks", .is_number = true },
+ { .name = "queue", .is_number = true },
+ { .name = "in_use", .is_number = true },
+ { .name = "max_in_use", .is_number = true },
+};
+
+RES_FUNC(res_frmr_pools, RDMA_NLDEV_CMD_RES_FRMR_POOLS_GET,
+ frmr_pools_valid_filters, true, 0);
+
void print_dev(uint32_t idx, const char *name);
void print_link(uint32_t idx, const char *name, uint32_t port, struct nlattr **nla_line);
void print_key(const char *name, uint64_t val, struct nlattr *nlattr);
--
2.38.1
^ permalink raw reply related
* [PATCH v2 iproute2-next 1/4] rdma: Update headers
From: Chiara Meiohas @ 2026-03-30 17:31 UTC (permalink / raw)
To: leon, dsahern, stephen
Cc: michaelgur, jgg, linux-rdma, netdev, Patrisious Haddad,
Chiara Meiohas
In-Reply-To: <20260330173118.766885-1-cmeiohas@nvidia.com>
From: Michael Guralnik <michaelgur@nvidia.com>
Update rdma_netlink.h file up to kernel commit dbd0472fd7a5
("RDMA/nldev: Expose kernel-internal FRMR pools in netlink")
Signed-off-by: Michael Guralnik <michaelgur@nvidia.com>
Reviewed-by: Patrisious Haddad <phaddad@nvidia.com>
Reviewed-by: Chiara Meiohas <cmeiohas@nvidia.com>
---
rdma/include/uapi/rdma/rdma_netlink.h | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/rdma/include/uapi/rdma/rdma_netlink.h b/rdma/include/uapi/rdma/rdma_netlink.h
index ec8c19ca..8709e558 100644
--- a/rdma/include/uapi/rdma/rdma_netlink.h
+++ b/rdma/include/uapi/rdma/rdma_netlink.h
@@ -308,6 +308,10 @@ enum rdma_nldev_command {
RDMA_NLDEV_CMD_MONITOR,
+ RDMA_NLDEV_CMD_RES_FRMR_POOLS_GET, /* can dump */
+
+ RDMA_NLDEV_CMD_RES_FRMR_POOLS_SET,
+
RDMA_NLDEV_NUM_OPS
};
@@ -582,6 +586,24 @@ enum rdma_nldev_attr {
RDMA_NLDEV_SYS_ATTR_MONITOR_MODE, /* u8 */
RDMA_NLDEV_ATTR_STAT_OPCOUNTER_ENABLED, /* u8 */
+
+ /*
+ * FRMR Pools attributes
+ */
+ RDMA_NLDEV_ATTR_RES_FRMR_POOLS, /* nested table */
+ RDMA_NLDEV_ATTR_RES_FRMR_POOL_ENTRY, /* nested table */
+ RDMA_NLDEV_ATTR_RES_FRMR_POOL_KEY, /* nested table */
+ RDMA_NLDEV_ATTR_RES_FRMR_POOL_KEY_ATS, /* u8 */
+ RDMA_NLDEV_ATTR_RES_FRMR_POOL_KEY_ACCESS_FLAGS, /* u32 */
+ RDMA_NLDEV_ATTR_RES_FRMR_POOL_KEY_VENDOR_KEY, /* u64 */
+ RDMA_NLDEV_ATTR_RES_FRMR_POOL_KEY_NUM_DMA_BLOCKS, /* u64 */
+ RDMA_NLDEV_ATTR_RES_FRMR_POOL_QUEUE_HANDLES, /* u32 */
+ RDMA_NLDEV_ATTR_RES_FRMR_POOL_MAX_IN_USE, /* u64 */
+ RDMA_NLDEV_ATTR_RES_FRMR_POOL_IN_USE, /* u64 */
+ RDMA_NLDEV_ATTR_RES_FRMR_POOL_AGING_PERIOD, /* u32 */
+ RDMA_NLDEV_ATTR_RES_FRMR_POOL_PINNED, /* u32 */
+ RDMA_NLDEV_ATTR_RES_FRMR_POOL_KEY_KERNEL_VENDOR_KEY, /* u64 */
+
/*
* Always the end
*/
--
2.38.1
^ permalink raw reply related
* [PATCH v2 iproute2-next 0/4] Introduce FRMR pools
From: Chiara Meiohas @ 2026-03-30 17:31 UTC (permalink / raw)
To: leon, dsahern, stephen
Cc: michaelgur, jgg, linux-rdma, netdev, Chiara Meiohas
From Michael:
This series adds support for managing Fast Registration Memory Region
(FRMR) pools in rdma tool, enabling users to monitor and configure FRMR
pool behavior.
FRMR pools are used to cache and reuse Fast Registration Memory Region
handles to improve performance by avoiding the overhead of repeated
memory region creation and destruction. This series introduces commands
to view FRMR pool statistics and configure pool parameters such as
aging time and pinned handle count.
The 'show' command allows users to display FRMR pools created on
devices, their properties, and usage statistics. Each pool is identified
by a unique key (hex-encoded properties) for easy reference in
subsequent operations.
The aging 'set' command allows users to modify the aging time parameter,
which controls how long unused FRMR handles remain in the pool before
being released.
The pinned 'set' command allows users to configure the number of pinned
handles in a pool. Pinned handles are exempt from aging and remain
permanently available for reuse, which is useful for workloads with
predictable memory region usage patterns.
Command usage and examples are included in the commits and man pages.
These patches are complimentary to the kernel patches:
https://lore.kernel.org/linux-rdma/20260226-frmr_pools-v4-0-95360b54f15e@nvidia.com/
--
v1->v2:
- frmr_pool_key: Replaced packed union with plain struct
- moved to colon-separated fields output format for pool keys instead of hex
strings
- Fix commented types of AGING_PERIOD and PINNED
- Add res_frmr_pools_idx_parse_cb stub
Michael Guralnik (4):
rdma: Update headers
rdma: Add resource FRMR pools show command
rdma: Add FRMR pools set aging command
rdma: Add FRMR pools set pinned command
man/man8/rdma-resource.8 | 51 +++-
rdma/Makefile | 2 +-
rdma/include/uapi/rdma/rdma_netlink.h | 22 ++
rdma/res-frmr-pools.c | 352 ++++++++++++++++++++++++++
rdma/res.c | 19 +-
rdma/res.h | 20 ++
6 files changed, 463 insertions(+), 3 deletions(-)
create mode 100644 rdma/res-frmr-pools.c
--
2.38.1
^ permalink raw reply
* Re: [net-next v6 12/12] selftests: drv-net: Add USO test
From: Joe Damato @ 2026-03-30 17:25 UTC (permalink / raw)
To: Jakub Kicinski
Cc: netdev, Shuah Khan, andrew+netdev, davem, edumazet, pabeni, horms,
michael.chan, pavan.chebbi, linux-kernel, leon, linux-kselftest
In-Reply-To: <20260329153130.61b29766@kernel.org>
On Sun, Mar 29, 2026 at 03:31:30PM -0700, Jakub Kicinski wrote:
> On Thu, 26 Mar 2026 16:52:31 -0700 Joe Damato wrote:
> > Add a simple test for USO. Can be used with netdevsim or real hardware.
> > Tests both ipv4 and ipv6 with several full segments and a partial
> > segment.
>
>
> > +def _test_uso(cfg, ipver, mss, total_payload):
> > + cfg.require_ipver(ipver)
> > +
> > + try:
> > + ethtool(f"-K {cfg.ifname} tx-udp-segmentation on")
> > + except Exception as exc:
> > + raise KsftSkipEx(
> > + "Device does not support tx-udp-segmentation") from exc
> > + defer(ethtool, f"-K {cfg.ifname} tx-udp-segmentation off")
>
> If may have been on already when we started, no?
Right, yea. I'll fix it. Is there a helper that does this generically for any
features, by any chance?
> > + expected_segs = (total_payload + mss - 1) // mss
> > +
> > + rx_before = _get_rx_packets(cfg)
>
> Let's run a little program on the remote that receives the packets
> and validates they were correct? Maybe socat can do?
Sure, I can do that in the v7.
> > + port = rand_port(stype=socket.SOCK_DGRAM)
> > + _send_uso(cfg, ipver, mss, total_payload, port)
> > +
> > + time.sleep(0.5)
>
> What's this? Did you mean to wait for stats to settle?
> cfg.wait_hw_stats_settle()
Yea. cfg.wait_hw_stats_settle is clearly better, thanks.
> > + rx_after = _get_rx_packets(cfg)
> > + rx_delta = rx_after - rx_before
>
> Instead of checking Rx (which is probably fine) we may want to check Tx
> has the right number of frames. Sender may have mis-counted the whole
> USO as one packet if it's buggy?
Sure.
> > + ksft_ge(rx_delta, expected_segs,
> > + comment=f"Expected >= {expected_segs} rx packets, got {rx_delta}")
> > +
> > +
> > +def test_uso_v4(cfg):
> > + """USO IPv4: 11 segments (10 full + 1 partial)."""
> > + _test_uso(cfg, "4", 1400, 1400 * 10 + 500)
> > +
> > +
> > +def test_uso_v6(cfg):
> > + """USO IPv6: 11 segments (10 full + 1 partial)."""
> > + _test_uso(cfg, "6", 1400, 1400 * 10 + 500)
> > +
> > +
> > +def test_uso_v4_exact(cfg):
> > + """USO IPv4: exact multiple of MSS (5 full segments)."""
> > + _test_uso(cfg, "4", 1400, 1400 * 5)
>
> Variants are probably a good fit here.
Is the right way to do this by using test_builder?
^ permalink raw reply
* Re: [PATCH 1/1] net: fix skb_ext_total_length() BUILD_BUG_ON with CONFIG_GCOV_PROFILE_ALL
From: Konstantin Khorenko @ 2026-03-30 17:23 UTC (permalink / raw)
To: Thomas Weißschuh
Cc: davem, edumazet, kuba, pabeni, horms, arnd, netdev, linux-kernel
In-Reply-To: <9e6d5484-3670-40fa-8c09-201b76cd0088@t-8ch.de>
On 3/30/26 16:48, Thomas Weißschuh wrote:
> On 2026-03-30 16:53:37+0300, Konstantin Khorenko wrote:
>> When CONFIG_GCOV_PROFILE_ALL=y is enabled, GCC inserts branch profiling
>> counters into skb_ext_total_length() and, combined with -fno-tree-loop-im
>> from GCOV, prevents the compiler from constant-folding the loop that
>> sums skb_ext_type_len[] array elements. This causes the compile-time
>> BUILD_BUG_ON(skb_ext_total_length() > 255) check to fail, even though
>> the actual computed value is well below 255.
>>
>> The kernel already has a guard for CONFIG_KCOV_INSTRUMENT_ALL, which
>> causes the same problem. Add a similar guard for GCOV.
>>
>> The number of loop iterations matters: with 4 extension types (as in
>> earlier kernels), GCC 11 can still constant-fold despite GCOV. With 5+
>> types (after SKB_EXT_CAN and other additions), it gives up. This is
>> why the issue only manifests in recent kernels with GCOV enabled.
>>
>> Tested with GCC 11.4.1 and GCC 16.0.1 20260327 (experimental) - both
>> exhibit the same behavior.
>>
>> Note that skb_ext_total_length() is still correct at runtime; this
>> change only allows the build to succeed when GCOV_PROFILE_ALL is
>> enabled for coverage analysis.
>>
>> Fixes: 5d21d0a65b57 ("net: generalize calculation of skb extensions length")
>>
>> Signed-off-by: Konstantin Khorenko <khorenko@virtuozzo.com>
>
> Reviewed-by: Thomas Weißschuh <linux@weissschuh.net>
Thank you very much for the review, Tomas!
> Would it help to mark skb_ext_total_length() as
> 'notrace'/'no_instrument_function'?
That's interesting.
"notrace" did not help, after all it just disables ftrace instrumentation in the beginning of functions.
But! i have also tried __no_profile (__no_profile_instrument_function__) and it helps!
-static __always_inline unsigned int skb_ext_total_length(void)
+static __always_inline __no_profile unsigned int skb_ext_total_length(void)
__no_profile_instrument_function__ tells GCC not to insert GCOV counters
into this specific function,
so without GCOV counters in the loop body, the loop becomes "clean" and
even though -fno-tree-loop-im is still active globally,
gcc can now constant-fold this clean loop.
As a result skb_ext_total_length() is fully evaluated at compile time and BUILD_BUG_ON succeeds.
Thus we can probably use this hack and create the following patch instead:
diff --git a/net/core/skbuff.c b/net/core/skbuff.c
index 0e217041958a..47c7f0ab6e84 100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -5145,7 +5145,7 @@ static const u8 skb_ext_type_len[] = {
#endif
};
-static __always_inline unsigned int skb_ext_total_length(void)
+static __always_inline __no_profile unsigned int skb_ext_total_length(void)
{
unsigned int l = SKB_EXT_CHUNKSIZEOF(struct skb_ext);
int i;
@@ -5159,9 +5159,7 @@ static __always_inline unsigned int skb_ext_total_length(void)
static void skb_extensions_init(void)
{
BUILD_BUG_ON(SKB_EXT_NUM > 8);
-#if !IS_ENABLED(CONFIG_KCOV_INSTRUMENT_ALL)
BUILD_BUG_ON(skb_ext_total_length() > 255);
-#endif
skbuff_ext_cache = kmem_cache_create("skbuff_ext_cache",
SKB_EXT_ALIGN_VALUE * skb_ext_total_length(),
Tomas, do you want me to send the v2 patch with that solution?
--
Best regards,
Konstantin Khorenko,
Virtuozzo Linux Kernel Team
^ permalink raw reply related
* Re: [net-next v6 11/12] net: netdevsim: Add support for SW USO
From: Joe Damato @ 2026-03-30 17:12 UTC (permalink / raw)
To: Jakub Kicinski
Cc: netdev, David S. Miller, Eric Dumazet, Paolo Abeni, andrew+netdev,
horms, michael.chan, pavan.chebbi, linux-kernel, leon
In-Reply-To: <20260329152640.00d52107@kernel.org>
On Sun, Mar 29, 2026 at 03:26:40PM -0700, Jakub Kicinski wrote:
> On Thu, 26 Mar 2026 16:52:30 -0700 Joe Damato wrote:
> > Add support for UDP Segmentation Offloading in software (SW USO). This
> > is helpful for testing when real hardware is not available. A test which
> > uses this codepath will be added in a following commit.
>
> Not sure we need to carry this in the tree.
> We have a bnxt device in NIPA, the code will be exercised.
OK, I will drop this in the v7. I didn't realize NIPA had a bnxt, but that
makes sense.
^ permalink raw reply
* Re: [REPORT] ipv4: nexthop: reachable WARN in rtm_get_nexthop() via oversized group dump
From: Fernando Fernandez Mancera @ 2026-03-30 17:11 UTC (permalink / raw)
To: 钱一铭, security; +Cc: netdev
In-Reply-To: <CAL_bE8Li2h4KO+AQFXW4S6Yb_u5X4oSKnkywW+LPFjuErhqELA@mail.gmail.com>
On 3/30/26 2:14 PM, 钱一铭 wrote:
> Hello,
>
> I found a reachable warning in `rtm_get_nexthop()` in upstream commit
> `a989fde763f4` (`7.0.0-rc4-00029-ga989fde763f4` in my test build).
> By creating a sufficiently large IPv4 nexthop group and then issuing
> `RTM_GETNEXTHOP` for that ID, a local user can trigger
> `WARN_ON(err == -EMSGSIZE)` in `net/ipv4/nexthop.c:3393`.
>
> With `panic_on_warn=1`, this becomes a deterministic local kernel DoS.
> Without `panic_on_warn=1`, the issue is still reachable as a user-triggerable
> kernel warning.
>
> Source analysis
>
> At the tested commit, `rtm_get_nexthop()` allocates a fixed-size reply skb:
>
> - `alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL)` at `net/ipv4/nexthop.c:3381`
>
> It then serializes the selected nexthop object into that skb:
>
> - `nh_fill_node(...)` at `net/ipv4/nexthop.c:3390`
>
> For group nexthops, `nh_fill_node()` emits `NHA_GROUP` and related metadata.
> If the skb is too small, the serialization helpers return `-EMSGSIZE`, and
> `nh_fill_node()` propagates that error.
>
> The problem is that group creation-time validation does not bound the group
> size against the future `RTM_GETNEXTHOP` response size. In particular,
> `nh_check_attr_group()` validates layout, duplicate IDs, reserved fields, and
> weights, but it does not reject a group that is valid to create and later too
> large to dump back through the fixed-size `RTM_GETNEXTHOP` reply path.
>
> The same file already has exact size estimation for nexthop dumps:
>
> - `nh_nlmsg_size()` computes the size needed for a given nexthop object
> - `nexthop_notify()` uses `nlmsg_new(nh_nlmsg_size(nh), ...)`
>
> So the reachable warning appears to come from a mismatch between:
>
> 1. object creation, which accepts very large groups, and
> 2. object query through `rtm_get_nexthop()`, which still uses a fixed
> `NLMSG_GOODSIZE` buffer.
>
> Why 512 members are enough
>
> `struct nexthop_grp` is 8 bytes. A 512-member group therefore needs a
> 4096-byte `NHA_GROUP` payload alone, before netlink attribute headers,
> alignment, `NHA_ID`, `NHA_GROUP_TYPE`, `NHA_OP_FLAGS`, and the `nhmsg`
> header are added. In practice, 256-member and 384-member groups were dumped
> successfully in my setup, while the first 512-member query reproducibly hit
> the warning.
>
Hi, thanks for the report. This issue is legit, I reproduced it locally
too. IMO, that warning and also the ones at nexthop_notify() and
rtm_get_nexthop_bucket() should be converted to the DEBUG_NET_ variant.
Using nh_nlmsg_size() seems like the right fix but the allocation of skb
would need to wait until nh fetched. I also wonder if we should use
nlmsg_new() directly. Would look deeper into it.
In addition I am writing a selftest patch for this.
Thanks,
Fernando.
^ permalink raw reply
* Re: [net-next v6 09/12] net: bnxt: Add SW GSO completion and teardown support
From: Joe Damato @ 2026-03-30 17:07 UTC (permalink / raw)
To: Jakub Kicinski
Cc: netdev, Michael Chan, David S. Miller, Eric Dumazet, Paolo Abeni,
andrew+netdev, horms, pavan.chebbi, linux-kernel, leon
In-Reply-To: <20260329152236.6ad729e6@kernel.org>
On Sun, Mar 29, 2026 at 03:22:36PM -0700, Jakub Kicinski wrote:
> On Thu, 26 Mar 2026 16:52:28 -0700 Joe Damato wrote:
> > + if (head_buf->is_sw_gso == BNXT_SW_GSO_LAST) {
> > + if (dma_use_iova(&head_buf->iova_state))
> > + dma_iova_destroy(&pdev->dev,
> > + &head_buf->iova_state,
> > + head_buf->iova_total_len,
> > + DMA_TO_DEVICE, 0);
>
> Do we have to expose the dma_use_iova() stuff to the driver at all?
> Could we have a function the driver is supposed to call to clean up,
> always, and what the function does is up to the TSO lib?
I could add a tso_dma_map_destroy(dev, iova_state, len) that the driver calls,
but the driver would still need to stash iova_state and total_len on the ring.
That would be easiest, but I'm not sure if you were thinking that the IOVA
stuff should be as opague as possible?
Because if you do want it to be as opague as possible, maybe:
/* Add a struct to tso.h to track completion state */
struct tso_dma_map_completion_state {
struct dma_iova_state iova_state;
size_t total_len;
}
Add a save function: tso_dma_map_completion_save(map, completion_state);
And then:
- the bnxt sw bd stores a struct tso_dma_map_completion_state.
- xmit calls tso_dma_map_completion_save to store the iova_state
- completion calls tso_dma_complete(dev, &head_buf->completion_state)
LMK if you meant the easier way or the more opague way?
^ permalink raw reply
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