Netdev List
 help / color / mirror / Atom feed
* [PATCH net-next 0/4] net: dsa: mt7628 embedded switch initial support
From: Joris Vaisvila @ 2026-03-26 20:44 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 RFC v3:
	- remove labels from DT binding
	- set ds->user_mii_bus if mdio node does not exist
Link: https://lore.kernel.org/netdev/20260321194340.2140783-1-joey@tinyisr.com

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                       |   7 +
 drivers/net/dsa/Makefile                      |   1 +
 drivers/net/dsa/mt7628.c                      | 637 ++++++++++++++++++
 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                          |  92 +++
 11 files changed, 907 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 v2] net/sched: sch_hfsc: fix divide-by-zero in rtsc_min()
From: Xiang Mei @ 2026-03-26 20:44 UTC (permalink / raw)
  To: Jakub Kicinski
  Cc: security, netdev, pabeni, jhs, jiri, davem, edumazet, horms,
	bestswngs
In-Reply-To: <20260326122137.4aae1d46@kernel.org>

Thanks for the suggestion. A TDC selftest was added to v3.

On Thu, Mar 26, 2026 at 12:21 PM Jakub Kicinski <kuba@kernel.org> wrote:
>
> On Thu, 26 Mar 2026 11:54:52 -0700 Xiang Mei wrote:
> > PoC:
> > ```sh
> > #!/bin/sh
> > ip link set lo up
> >
> > tc qdisc replace dev lo root handle 1: \
> >     stab overhead 3000000 mtu 0 tsize 0 \
> >     hfsc default 1
> > tc class replace dev lo parent 1: classid 1:1 hfsc \
> >     rt m1 32gbit d 1ms m2 0bit \
> >     ls m1 32gbit d 1ms m2 0bit
> >
> > # Send UDP to loopback (same method as C PoC)
> > python3 -c "
> > import socket,time
> > s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
> > s.sendto(b'A'*64,('127.0.0.1',1337))  # activate class
> > time.sleep(1)                           # drain → idle
> > s.sendto(b'A'*64,('127.0.0.1',1337))  # reactivation → div-by-zero
> > "
> > ```
>
> Please turn this into a TDC selftest.

^ permalink raw reply

* [PATCH net-next 4/4] net: dsa: initial support for MT7628 embedded switch
From: Joris Vaisvila @ 2026-03-26 20:44 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: <20260326204413.3317584-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  |   7 +
 drivers/net/dsa/Makefile |   1 +
 drivers/net/dsa/mt7628.c | 637 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 645 insertions(+)
 create mode 100644 drivers/net/dsa/mt7628.c

diff --git a/drivers/net/dsa/Kconfig b/drivers/net/dsa/Kconfig
index 39fb8ead16b5..d07fc8dfe228 100644
--- a/drivers/net/dsa/Kconfig
+++ b/drivers/net/dsa/Kconfig
@@ -70,6 +70,13 @@ config NET_DSA_MV88E6060
 	  This enables support for the Marvell 88E6060 ethernet switch
 	  chip.
 
+config NET_DSA_MT7628
+	tristate "MT7628 Embedded ethernet switch support"
+	select NET_DSA_TAG_MT7628
+	select MEDIATEK_FE_SOC_PHY
+	help
+	  This enables support for the switch in the MT7628 SoC.
+
 source "drivers/net/dsa/microchip/Kconfig"
 
 source "drivers/net/dsa/mv88e6xxx/Kconfig"
diff --git a/drivers/net/dsa/Makefile b/drivers/net/dsa/Makefile
index f5a463b87ec2..22da6b680f29 100644
--- a/drivers/net/dsa/Makefile
+++ b/drivers/net/dsa/Makefile
@@ -15,6 +15,7 @@ obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX) += vitesse-vsc73xx-core.o
 obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX_PLATFORM) += vitesse-vsc73xx-platform.o
 obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX_SPI) += vitesse-vsc73xx-spi.o
 obj-$(CONFIG_NET_DSA_YT921X) += yt921x.o
+obj-$(CONFIG_NET_DSA_MT7628) += mt7628.o
 obj-y				+= b53/
 obj-y				+= hirschmann/
 obj-y				+= lantiq/
diff --git a/drivers/net/dsa/mt7628.c b/drivers/net/dsa/mt7628.c
new file mode 100644
index 000000000000..76ce5cd3c435
--- /dev/null
+++ b/drivers/net/dsa/mt7628.c
@@ -0,0 +1,637 @@
+// 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 -ETIMEDOUT;
+	}
+	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 -ETIMEDOUT;
+	}
+	return ret;
+}
+
+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_child_by_name(ds->dev->of_node, "mdio");
+	if (mdio && !of_device_is_available(mdio))
+		goto out_put_node;
+
+	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 0:
+	case 1:
+	case 2:
+	case 3:
+	case 4:
+		config->mac_capabilities = MAC_100 | MAC_10;
+		__set_bit(PHY_INTERFACE_MODE_INTERNAL,
+			  config->supported_interfaces);
+		break;
+	case 6:
+		config->mac_capabilities = MAC_1000 | MAC_100 | MAC_10;
+		__set_bit(PHY_INTERFACE_MODE_RGMII,
+			  config->supported_interfaces);
+		break;
+	case 5:
+	default:
+		/* Port 5 does not exist on MT7628; other ports are invalid */
+		return;
+	}
+}
+
+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

* Re: [PATCH nf] netfilter: nf_conntrack_sip: fix use of uninitialized rtp_addr in process_sdp
From: Florian Westphal @ 2026-03-26 20:44 UTC (permalink / raw)
  To: Pablo Neira Ayuso
  Cc: Weiming Shi, David S . Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Phil Sutter, Simon Horman, netfilter-devel, coreteam,
	netdev, linux-kernel, Xiang Mei
In-Reply-To: <acWVV__8xRMezYrU@chamomile>

Pablo Neira Ayuso <pablo@netfilter.org> wrote:
> > @@ -1091,9 +1095,11 @@ static int process_sdp(struct sk_buff *skb, unsigned int protoff,
> >  					  &matchoff, &matchlen, &maddr) > 0) {
> >  			maddr_len = matchlen;
> >  			memcpy(&rtp_addr, &maddr, sizeof(rtp_addr));
> > -		} else if (caddr_len)
> > +			have_rtp_addr = true;
> > +		} else if (caddr_len) {
> >  			memcpy(&rtp_addr, &caddr, sizeof(rtp_addr));
> > -		else {
> > +			have_rtp_addr = true;
> 
> After this update, this loop sets over rtp_addr, but this was already
> set by ct_sip_parse_sdp_addr() a bit above.
> 
> This new chunk results in:
> 
>                 } else if (caddr_len) {
>                        memcpy(&rtp_addr, &caddr, sizeof(rtp_addr));
>                        have_rtp_addr = true;
> 
> which is not needed? Why does caddr need to be copied over and over
> again to rtp_addr?

Code before update was:
                if (ct_sip_parse_sdp_addr(ct, *dptr, mediaoff, *datalen,
                                          SDP_HDR_CONNECTION, SDP_HDR_MEDIA,
                                          &matchoff, &matchlen, &maddr) > 0) {
                        maddr_len = matchlen;
                        memcpy(&rtp_addr, &maddr, sizeof(rtp_addr));
                } else if (caddr_len)
                        memcpy(&rtp_addr, &caddr, sizeof(rtp_addr));
                else {


so we re-set rtp_addr to the session description in case it was
overwritten by earlier iteration of the loop.

1. Extract session description (caddr_len set)
2. enter loop, parse media description (overwrite rtp_addr with media
   address)
3. next loop fails ct_sip_parse_sdp_addr() call
   Restore the original session address instead of using
   the previous media description.

I think its correct this way.

^ permalink raw reply

* [PATCH net-next 3/4] net: dsa: initial MT7628 tagging driver
From: Joris Vaisvila @ 2026-03-26 20:44 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: <20260326204413.3317584-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 | 92 ++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 101 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..4aa73bd1aa9b 100644
--- a/net/dsa/Kconfig
+++ b/net/dsa/Kconfig
@@ -211,4 +211,10 @@ config NET_DSA_TAG_YT921X
 	  Say Y or M if you want to enable support for tagging frames for
 	  Motorcomm YT921x switches.
 
+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.
+
 endif
diff --git a/net/dsa/Makefile b/net/dsa/Makefile
index bf7247759a64..d25ec0ab7d67 100644
--- a/net/dsa/Makefile
+++ b/net/dsa/Makefile
@@ -42,6 +42,7 @@ obj-$(CONFIG_NET_DSA_TAG_TRAILER) += tag_trailer.o
 obj-$(CONFIG_NET_DSA_TAG_VSC73XX_8021Q) += tag_vsc73xx_8021q.o
 obj-$(CONFIG_NET_DSA_TAG_XRS700X) += tag_xrs700x.o
 obj-$(CONFIG_NET_DSA_TAG_YT921X) += tag_yt921x.o
+obj-$(CONFIG_NET_DSA_TAG_MT7628) += tag_mt7628.o
 
 # for tracing framework to find trace.h
 CFLAGS_trace.o := -I$(src)
diff --git a/net/dsa/tag_mt7628.c b/net/dsa/tag_mt7628.c
new file mode 100644
index 000000000000..ef119c37b26a
--- /dev/null
+++ b/net/dsa/tag_mt7628.c
@@ -0,0 +1,92 @@
+// 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_BIT_MASK GENMASK(5, 0)
+#define MT7628_TAG_RX_PORT_MASK 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_BIT_MASK,
+				  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)
+{
+	int src_port;
+	__be16 *phdr;
+	u16 tpid;
+
+	if (unlikely(!pskb_may_pull(skb, MT7628_TAG_LEN)))
+		return NULL;
+
+	phdr = dsa_etype_header_pos_rx(skb);
+	tpid = ntohs(*phdr);
+	skb_pull_rcsum(skb, MT7628_TAG_LEN);
+	dsa_strip_etype_header(skb, MT7628_TAG_LEN);
+
+	src_port = tpid & MT7628_TAG_RX_PORT_MASK;
+
+	skb->dev = dsa_conduit_find_user(dev, 0, src_port);
+	if (!skb->dev)
+		return NULL;
+	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 v3 2/2] selftests/tc-testing: add test for HFSC divide-by-zero in rtsc_min()
From: Xiang Mei @ 2026-03-26 20:43 UTC (permalink / raw)
  To: security
  Cc: netdev, pabeni, jhs, jiri, davem, edumazet, kuba, horms,
	bestswngs, Xiang Mei
In-Reply-To: <20260326204310.1549327-1-xmei5@asu.edu>

Add a regression test for the divide-by-zero in rtsc_min() triggered
when m2sm() converts a large m1 value (e.g. 32gbit) to a u64 scaled
slope reaching 2^32. rtsc_min() stores the difference of two such u64
values (sm1 - sm2) in a u32 variable `dsm`, truncating 2^32 to zero
and causing a divide-by-zero oops in the concave-curve intersection
path. The test configures an HFSC class with m1=32gbit d=1ms m2=0bit,
sends a packet to activate the class, waits for it to drain and go
idle, then sends another packet to trigger reactivation through
rtsc_min().

Signed-off-by: Xiang Mei <xmei5@asu.edu>
---
v3: append selftest in 2/2

 .../tc-testing/tc-tests/infra/qdiscs.json     | 25 +++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/tools/testing/selftests/tc-testing/tc-tests/infra/qdiscs.json b/tools/testing/selftests/tc-testing/tc-tests/infra/qdiscs.json
index 6a39640aa2a8..1e5efb2a31eb 100644
--- a/tools/testing/selftests/tc-testing/tc-tests/infra/qdiscs.json
+++ b/tools/testing/selftests/tc-testing/tc-tests/infra/qdiscs.json
@@ -1111,5 +1111,30 @@
         "teardown": [
             "$TC qdisc del dev $DUMMY root handle 1:"
         ]
+    },
+    {
+        "id": "a3d7",
+        "name": "HFSC with large m1 - no divide-by-zero on class reactivation",
+        "category": [
+            "qdisc",
+            "hfsc"
+        ],
+        "plugins": {
+            "requires": "nsPlugin"
+        },
+        "setup": [
+            "$TC qdisc replace dev $DUMMY root handle 1: hfsc default 1",
+            "$TC class replace dev $DUMMY parent 1: classid 1:1 hfsc rt m1 32gbit d 1ms m2 0bit ls m1 32gbit d 1ms m2 0bit",
+            "ping -I$DUMMY -f -c1 -s64 -W1 10.10.10.1 || true",
+            "sleep 1"
+        ],
+        "cmdUnderTest": "ping -I$DUMMY -f -c1 -s64 -W1 10.10.10.1 || true",
+        "expExitCode": "0",
+        "verifyCmd": "$TC qdisc show dev $DUMMY",
+        "matchPattern": "qdisc hfsc 1: root",
+        "matchCount": "1",
+        "teardown": [
+            "$TC qdisc del dev $DUMMY handle 1: root"
+        ]
     }
 ]
-- 
2.43.0


^ permalink raw reply related

* [PATCH net v3 1/2] net/sched: sch_hfsc: fix divide-by-zero in rtsc_min()
From: Xiang Mei @ 2026-03-26 20:43 UTC (permalink / raw)
  To: security
  Cc: netdev, pabeni, jhs, jiri, davem, edumazet, kuba, horms,
	bestswngs, Xiang Mei

m2sm() converts a u32 slope to a u64 scaled value.  For large inputs
(e.g. m1=4000000000), the result can reach 2^32.  rtsc_min() stores
the difference of two such u64 values in a u32 variable `dsm` and
uses it as a divisor.  When the difference is exactly 2^32 the
truncation yields zero, causing a divide-by-zero oops in the
concave-curve intersection path:

  Oops: divide error: 0000
  RIP: 0010:rtsc_min (net/sched/sch_hfsc.c:601)
  Call Trace:
   init_ed (net/sched/sch_hfsc.c:629)
   hfsc_enqueue (net/sched/sch_hfsc.c:1569)
   [...]

Widen `dsm` to u64 and replace do_div() with div64_u64() so the full
difference is preserved.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Reported-by: Weiming Shi <bestswngs@gmail.com>
Signed-off-by: Xiang Mei <xmei5@asu.edu>
---
v2: resend to netdev ML
v3: append selftest in 2/2
 
 net/sched/sch_hfsc.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/net/sched/sch_hfsc.c b/net/sched/sch_hfsc.c
index b5657ffbbf84..83b2ca2e37fc 100644
--- a/net/sched/sch_hfsc.c
+++ b/net/sched/sch_hfsc.c
@@ -555,7 +555,7 @@ static void
 rtsc_min(struct runtime_sc *rtsc, struct internal_sc *isc, u64 x, u64 y)
 {
 	u64 y1, y2, dx, dy;
-	u32 dsm;
+	u64 dsm;
 
 	if (isc->sm1 <= isc->sm2) {
 		/* service curve is convex */
@@ -598,7 +598,7 @@ rtsc_min(struct runtime_sc *rtsc, struct internal_sc *isc, u64 x, u64 y)
 	 */
 	dx = (y1 - y) << SM_SHIFT;
 	dsm = isc->sm1 - isc->sm2;
-	do_div(dx, dsm);
+	dx = div64_u64(dx, dsm);
 	/*
 	 * check if (x, y1) belongs to the 1st segment of rtsc.
 	 * if so, add the offset.
-- 
2.43.0


^ permalink raw reply related

* [PATCH net-next v2 3/3] vrf: Remove unnecessary RCU protection around dst entries
From: Ido Schimmel @ 2026-03-26 20:32 UTC (permalink / raw)
  To: netdev
  Cc: davem, kuba, pabeni, edumazet, dsahern, andrew+netdev, petrm,
	lirongqing, Ido Schimmel
In-Reply-To: <20260326203233.1128554-1-idosch@nvidia.com>

During initialization of a VRF device, the VRF driver creates two dst
entries (for IPv4 and IPv6). They are attached to locally generated
packets that are transmitted out of the VRF ports (via the
l3mdev_l3_out() hook). Their purpose is to redirect packets towards the
VRF device instead of having the packets egress directly out of the VRF
ports. This is useful, for example, when a queuing discipline is
configured on the VRF device.

In order to avoid a NULL pointer dereference, commit b0e95ccdd775 ("net:
vrf: protect changes to private data with rcu") made the pointers to the
dst entries RCU protected. As far as I can tell, this was needed because
back then the dst entries were released (and the pointers reset to NULL)
before removing the VRF ports.

Later on, commit f630c38ef0d7 ("vrf: fix bug_on triggered by rx when
destroying a vrf") moved the removal of the VRF ports to the VRF
device's dellink() callback. As such, the tear down sequence of a VRF
device looks as follows:

1. VRF ports are removed.
2. VRF device is unregistered.
    a. Device is closed.
    b. An RCU grace period passes.
    c. ndo_uninit() is called.
        i. dst entries are released.

Given the above, the Tx path will always see the same fully initialized
dst entries and will never race with the ndo_uninit() callback.

Therefore, there is no need to make the pointers to the dst entries RCU
protected. Remove it as well as the unnecessary NULL checks in the Tx
path.

Signed-off-by: Ido Schimmel <idosch@nvidia.com>
---
 drivers/net/vrf.c | 56 ++++++++++-------------------------------------
 1 file changed, 12 insertions(+), 44 deletions(-)

diff --git a/drivers/net/vrf.c b/drivers/net/vrf.c
index bfc9ea91ac20..2cf2dbd1c12f 100644
--- a/drivers/net/vrf.c
+++ b/drivers/net/vrf.c
@@ -112,8 +112,8 @@ struct netns_vrf {
 };
 
 struct net_vrf {
-	struct rtable __rcu	*rth;
-	struct rt6_info	__rcu	*rt6;
+	struct rtable		*rth;
+	struct rt6_info		*rt6;
 #if IS_ENABLED(CONFIG_IPV6)
 	struct fib6_table	*fib6_table;
 #endif
@@ -648,26 +648,13 @@ static struct sk_buff *vrf_ip6_out_redirect(struct net_device *vrf_dev,
 					    struct sk_buff *skb)
 {
 	struct net_vrf *vrf = netdev_priv(vrf_dev);
-	struct dst_entry *dst = NULL;
 	struct rt6_info *rt6;
 
-	rcu_read_lock();
-
-	rt6 = rcu_dereference(vrf->rt6);
-	if (likely(rt6)) {
-		dst = &rt6->dst;
-		dst_hold(dst);
-	}
-
-	rcu_read_unlock();
-
-	if (unlikely(!dst)) {
-		vrf_tx_error(vrf_dev, skb);
-		return NULL;
-	}
+	rt6 = vrf->rt6;
+	dst_hold(&rt6->dst);
 
 	skb_dst_drop(skb);
-	skb_dst_set(skb, dst);
+	skb_dst_set(skb, &rt6->dst);
 
 	return skb;
 }
@@ -750,10 +737,7 @@ static struct sk_buff *vrf_ip6_out(struct net_device *vrf_dev,
 /* holding rtnl */
 static void vrf_rt6_release(struct net_device *dev, struct net_vrf *vrf)
 {
-	struct rt6_info *rt6 = rtnl_dereference(vrf->rt6);
-
-	RCU_INIT_POINTER(vrf->rt6, NULL);
-	synchronize_rcu();
+	struct rt6_info *rt6 = vrf->rt6;
 
 	if (rt6) {
 		dst_dev_put(&rt6->dst);
@@ -784,7 +768,7 @@ static int vrf_rt6_create(struct net_device *dev)
 
 	rt6->dst.output	= vrf_output6;
 
-	rcu_assign_pointer(vrf->rt6, rt6);
+	vrf->rt6 = rt6;
 
 	rc = 0;
 out:
@@ -870,26 +854,13 @@ static struct sk_buff *vrf_ip_out_redirect(struct net_device *vrf_dev,
 					   struct sk_buff *skb)
 {
 	struct net_vrf *vrf = netdev_priv(vrf_dev);
-	struct dst_entry *dst = NULL;
 	struct rtable *rth;
 
-	rcu_read_lock();
-
-	rth = rcu_dereference(vrf->rth);
-	if (likely(rth)) {
-		dst = &rth->dst;
-		dst_hold(dst);
-	}
-
-	rcu_read_unlock();
-
-	if (unlikely(!dst)) {
-		vrf_tx_error(vrf_dev, skb);
-		return NULL;
-	}
+	rth = vrf->rth;
+	dst_hold(&rth->dst);
 
 	skb_dst_drop(skb);
-	skb_dst_set(skb, dst);
+	skb_dst_set(skb, &rth->dst);
 
 	return skb;
 }
@@ -989,10 +960,7 @@ static struct sk_buff *vrf_l3_out(struct net_device *vrf_dev,
 /* holding rtnl */
 static void vrf_rtable_release(struct net_device *dev, struct net_vrf *vrf)
 {
-	struct rtable *rth = rtnl_dereference(vrf->rth);
-
-	RCU_INIT_POINTER(vrf->rth, NULL);
-	synchronize_rcu();
+	struct rtable *rth = vrf->rth;
 
 	dst_dev_put(&rth->dst);
 	dst_release(&rth->dst);
@@ -1013,7 +981,7 @@ static int vrf_rtable_create(struct net_device *dev)
 
 	rth->dst.output	= vrf_output;
 
-	rcu_assign_pointer(vrf->rth, rth);
+	vrf->rth = rth;
 
 	return 0;
 }
-- 
2.53.0


^ permalink raw reply related

* [PATCH net-next v2 2/3] vrf: Use dst_dev_put() instead of using loopback device
From: Ido Schimmel @ 2026-03-26 20:32 UTC (permalink / raw)
  To: netdev
  Cc: davem, kuba, pabeni, edumazet, dsahern, andrew+netdev, petrm,
	lirongqing, Ido Schimmel
In-Reply-To: <20260326203233.1128554-1-idosch@nvidia.com>

Use dst_dev_put() to clean up the device referenced by the dst entry
instead of partially open coding it. Internally, the helper uses the
blackhole device instead of the loopback device.

Reviewed-by: Petr Machata <petrm@nvidia.com>
Reviewed-by: David Ahern <dsahern@kernel.org>
Signed-off-by: Ido Schimmel <idosch@nvidia.com>
---
 drivers/net/vrf.c | 24 ++++--------------------
 1 file changed, 4 insertions(+), 20 deletions(-)

diff --git a/drivers/net/vrf.c b/drivers/net/vrf.c
index 0952ab6a2571..bfc9ea91ac20 100644
--- a/drivers/net/vrf.c
+++ b/drivers/net/vrf.c
@@ -751,21 +751,13 @@ static struct sk_buff *vrf_ip6_out(struct net_device *vrf_dev,
 static void vrf_rt6_release(struct net_device *dev, struct net_vrf *vrf)
 {
 	struct rt6_info *rt6 = rtnl_dereference(vrf->rt6);
-	struct net *net = dev_net(dev);
-	struct dst_entry *dst;
 
 	RCU_INIT_POINTER(vrf->rt6, NULL);
 	synchronize_rcu();
 
-	/* move dev in dst's to loopback so this VRF device can be deleted
-	 * - based on dst_ifdown
-	 */
 	if (rt6) {
-		dst = &rt6->dst;
-		netdev_ref_replace(dst->dev, net->loopback_dev,
-				   &dst->dev_tracker, GFP_KERNEL);
-		dst->dev = net->loopback_dev;
-		dst_release(dst);
+		dst_dev_put(&rt6->dst);
+		dst_release(&rt6->dst);
 	}
 }
 
@@ -998,20 +990,12 @@ static struct sk_buff *vrf_l3_out(struct net_device *vrf_dev,
 static void vrf_rtable_release(struct net_device *dev, struct net_vrf *vrf)
 {
 	struct rtable *rth = rtnl_dereference(vrf->rth);
-	struct net *net = dev_net(dev);
-	struct dst_entry *dst;
 
 	RCU_INIT_POINTER(vrf->rth, NULL);
 	synchronize_rcu();
 
-	/* move dev in dst's to loopback so this VRF device can be deleted
-	 * - based on dst_ifdown
-	 */
-	dst = &rth->dst;
-	netdev_ref_replace(dst->dev, net->loopback_dev,
-			   &dst->dev_tracker, GFP_KERNEL);
-	dst->dev = net->loopback_dev;
-	dst_release(dst);
+	dst_dev_put(&rth->dst);
+	dst_release(&rth->dst);
 }
 
 static int vrf_rtable_create(struct net_device *dev)
-- 
2.53.0


^ permalink raw reply related

* [PATCH net-next v2 1/3] vrf: Remove unnecessary NULL check
From: Ido Schimmel @ 2026-03-26 20:32 UTC (permalink / raw)
  To: netdev
  Cc: davem, kuba, pabeni, edumazet, dsahern, andrew+netdev, petrm,
	lirongqing, Ido Schimmel
In-Reply-To: <20260326203233.1128554-1-idosch@nvidia.com>

The VRF driver always allocates an IPv4 dst entry for a VRF device and
prevents the device from being registered if the allocation fails.

Therefore, there is no need to check if the entry exists when tearing
down a VRF device. Remove the check.

Note that the same is not true for the IPv6 dst entry. Its creation can
be skipped if IPv6 is administratively disabled (i.e.,
'ipv6.disable=1').

Reviewed-by: Petr Machata <petrm@nvidia.com>
Reviewed-by: David Ahern <dsahern@kernel.org>
Signed-off-by: Ido Schimmel <idosch@nvidia.com>
---
 drivers/net/vrf.c | 12 +++++-------
 1 file changed, 5 insertions(+), 7 deletions(-)

diff --git a/drivers/net/vrf.c b/drivers/net/vrf.c
index 8c009bcaa8e7..0952ab6a2571 100644
--- a/drivers/net/vrf.c
+++ b/drivers/net/vrf.c
@@ -1007,13 +1007,11 @@ static void vrf_rtable_release(struct net_device *dev, struct net_vrf *vrf)
 	/* move dev in dst's to loopback so this VRF device can be deleted
 	 * - based on dst_ifdown
 	 */
-	if (rth) {
-		dst = &rth->dst;
-		netdev_ref_replace(dst->dev, net->loopback_dev,
-				   &dst->dev_tracker, GFP_KERNEL);
-		dst->dev = net->loopback_dev;
-		dst_release(dst);
-	}
+	dst = &rth->dst;
+	netdev_ref_replace(dst->dev, net->loopback_dev,
+			   &dst->dev_tracker, GFP_KERNEL);
+	dst->dev = net->loopback_dev;
+	dst_release(dst);
 }
 
 static int vrf_rtable_create(struct net_device *dev)
-- 
2.53.0


^ permalink raw reply related

* [PATCH net-next v2 0/3] vrf: A few cleanups
From: Ido Schimmel @ 2026-03-26 20:32 UTC (permalink / raw)
  To: netdev
  Cc: davem, kuba, pabeni, edumazet, dsahern, andrew+netdev, petrm,
	lirongqing, Ido Schimmel

Perform a few cleanups in the VRF driver. Noticed these while reviewing
a recent patch [1]. See individual patches for more details.

[1] https://lore.kernel.org/netdev/20260310105331.2371-1-lirongqing@baidu.com/

v2:
* In patch #3, remove RCU protection around dst entries instead of just
the synchronize_rcu() calls.

v1: https://lore.kernel.org/netdev/20260324155521.525292-1-idosch@nvidia.com/

Ido Schimmel (3):
  vrf: Remove unnecessary NULL check
  vrf: Use dst_dev_put() instead of using loopback device
  vrf: Remove unnecessary RCU protection around dst entries

 drivers/net/vrf.c | 82 +++++++++--------------------------------------
 1 file changed, 16 insertions(+), 66 deletions(-)

-- 
2.53.0


^ permalink raw reply

* Re: [PATCH v8 02/10] x86/bhi: Make clear_bhb_loop() effective on newer CPUs
From: Pawan Gupta @ 2026-03-26 20:29 UTC (permalink / raw)
  To: David Laight
  Cc: Borislav Petkov, x86, Jon Kohler, Nikolay Borisov, H. Peter Anvin,
	Josh Poimboeuf, David Kaplan, Sean Christopherson, Dave Hansen,
	Peter Zijlstra, Alexei Starovoitov, Daniel Borkmann,
	Andrii Nakryiko, KP Singh, Jiri Olsa, David S. Miller,
	Andy Lutomirski, Thomas Gleixner, Ingo Molnar, David Ahern,
	Martin KaFai Lau, Eduard Zingerman, Song Liu, Yonghong Song,
	John Fastabend, Stanislav Fomichev, Hao Luo, Paolo Bonzini,
	Jonathan Corbet, linux-kernel, kvm, Asit Mallick, Tao Zhang, bpf,
	netdev, linux-doc
In-Reply-To: <20260326104557.24295cbb@pumpkin>

On Thu, Mar 26, 2026 at 10:45:57AM +0000, David Laight wrote:
> On Thu, 26 Mar 2026 11:01:20 +0100
> Borislav Petkov <bp@alien8.de> wrote:
> 
> > On Thu, Mar 26, 2026 at 01:39:34AM -0700, Pawan Gupta wrote:
> > > I believe the equivalent for cpu_feature_enabled() in asm is the
> > > ALTERNATIVE. Please let me know if I am missing something.  
> > 
> > Yes, you are.
> > 
> > The point is that you don't want to stick those alternative calls inside some
> > magic bhb_loop function but hand them in from the outside, as function
> > arguments.
> > 
> > Basically what I did.
> > 
> > Then you were worried about this being C code and it had to be noinstr... So
> > that outer function can be rewritten in asm, I think, and still keep it well
> > separate.
> > 
> > I'll try to rewrite it once I get a free minute, and see how it looks.
> > 
> 
> I think someone tried getting C code to write the values to global data
> and getting the asm to read them.
> That got discounted because it spilt things between two largely unrelated files.


The implementation with global variables wasn't that bad, let me revive it.

This part which ties sequence to BHI mitigation, which is not ideal,
(because VMSCAPE also uses it) it does seems a cleaner option.

--- a/arch/x86/kernel/cpu/bugs.c
+++ b/arch/x86/kernel/cpu/bugs.c
@@ -2095,6 +2095,11 @@ static void __init bhi_select_mitigation(void)

 static void __init bhi_update_mitigation(void)
 {
+   if (!cpu_feature_enabled(X86_FEATURE_BHI_CTRL)) {
+       bhi_seq_outer_loop = 5;
+       bhi_seq_inner_loop = 5;
+   }
+

I believe this can be moved to somewhere common to all mitigations.

> I think the BPF code would need significant refactoring to call a C function.

Ya, true. Will use globals and keep clear_bhb_loop() in asm.

^ permalink raw reply

* [PATCH net] ipv6: icmp: clear skb2->cb[] in ip6_err_gen_icmpv6_unreach()
From: Eric Dumazet @ 2026-03-26 20:26 UTC (permalink / raw)
  To: David S . Miller, Jakub Kicinski, Paolo Abeni
  Cc: Simon Horman, David Ahern, netdev, eric.dumazet, Eric Dumazet,
	Ido Schimmel, Oskar Kjos

Sashiko AI-review observed:

  In ip6_err_gen_icmpv6_unreach(), the skb is an outer IPv4 ICMP error packet
  where its cb contains an IPv4 inet_skb_parm. When skb is cloned into skb2
  and passed to icmp6_send(), it uses IP6CB(skb2).

  IP6CB interprets the IPv4 inet_skb_parm as an inet6_skb_parm. The cipso
  offset in inet_skb_parm.opt directly overlaps with dsthao in inet6_skb_parm
  at offset 18.

  If an attacker sends a forged ICMPv4 error with a CIPSO IP option, dsthao
  would be a non-zero offset. Inside icmp6_send(), mip6_addr_swap() is called
  and uses ipv6_find_tlv(skb, opt->dsthao, IPV6_TLV_HAO).

  This would scan the inner, attacker-controlled IPv6 packet starting at that
  offset, potentially returning a fake TLV without checking if the remaining
  packet length can hold the full 18-byte struct ipv6_destopt_hao.

  Could mip6_addr_swap() then perform a 16-byte swap that extends past the end
  of the packet data into skb_shared_info?

  Should the cb array also be cleared in ip6_err_gen_icmpv6_unreach() and
  ip6ip6_err() to prevent this?

This patch implements the first suggestion.

I am not sure if ip6ip6_err() needs to be changed.
A separate patch would be better anyway.

Fixes: ca15a078bd90 ("sit: generate icmpv6 error when receiving icmpv4 error")
Reported-by: Ido Schimmel <idosch@nvidia.com>
Closes: https://sashiko.dev/#/patchset/20260326155138.2429480-1-edumazet%40google.com
Signed-off-by: Eric Dumazet <edumazet@google.com>
Cc: Ido Schimmel <idosch@nvidia.com>
Cc: Oskar Kjos <oskar.kjos@hotmail.com>
---
 net/ipv6/icmp.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/net/ipv6/icmp.c b/net/ipv6/icmp.c
index 813d2e9edb8bed7c1649e279cea9229806af4132..d5d23a9296eac86a4a7ae8ea0240314653170035 100644
--- a/net/ipv6/icmp.c
+++ b/net/ipv6/icmp.c
@@ -875,6 +875,9 @@ int ip6_err_gen_icmpv6_unreach(struct sk_buff *skb, int nhs, int type,
 	if (!skb2)
 		return 1;
 
+	/* Remove debris left by IPv4 stack. */
+	memset(IP6CB(skb2), 0, sizeof(*IP6CB(skb2)));
+
 	skb_dst_drop(skb2);
 	skb_pull(skb2, nhs);
 	skb_reset_network_header(skb2);
-- 
2.53.0.1018.g2bb0e51243-goog


^ permalink raw reply related

* Re: [PATCH next] netfilter: nf_conntrack_h323: Correct indentation when H323_TRACE defined
From: Pablo Neira Ayuso @ 2026-03-26 20:24 UTC (permalink / raw)
  To: david.laight.linux
  Cc: Florian Westphal, Phil Sutter, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, netfilter-devel,
	coreteam, netdev, linux-kernel, Masami Hiramatsu, Petr Mladek,
	Rasmus Villemoes, Andy Shevchenko, Steven Rostedt,
	Sergey Senozhatsky, Andrew Morton
In-Reply-To: <20260326201819.3900-1-david.laight.linux@gmail.com>

Hi David,

On Thu, Mar 26, 2026 at 08:18:19PM +0000, david.laight.linux@gmail.com wrote:
> From: David Laight <david.laight.linux@gmail.com>
> 
> The trace lines are indented using PRINT("%*.s", xx, " ").
> Userspace will treat this as "%*.0s" and will output no characters
> when 'xx' is zero, the kernel treats it as "%*s" and will output
> a single ' ' - which is probably what is intended.
> 
> Change all the formats to "%*s" removing the default precision.
> This gives a single space indent when level is zero.

Do you have a setup using this helper? Or you just found this via
visual inspection?

> Signed-off-by: David Laight <david.laight.linux@gmail.com>
> ---
>  net/netfilter/nf_conntrack_h323_asn1.c | 38 +++++++++++++-------------
>  1 file changed, 19 insertions(+), 19 deletions(-)
> 
> diff --git a/net/netfilter/nf_conntrack_h323_asn1.c b/net/netfilter/nf_conntrack_h323_asn1.c
> index 7b1497ed97d2..287402428975 100644
> --- a/net/netfilter/nf_conntrack_h323_asn1.c
> +++ b/net/netfilter/nf_conntrack_h323_asn1.c
> @@ -276,7 +276,7 @@ static unsigned int get_uint(struct bitstr *bs, int b)
>  static int decode_nul(struct bitstr *bs, const struct field_t *f,
>                        char *base, int level)
>  {
> -	PRINT("%*.s%s\n", level * TAB_SIZE, " ", f->name);
> +	PRINT("%*s%s\n", level * TAB_SIZE, " ", f->name);
>  
>  	return H323_ERROR_NONE;
>  }
> @@ -284,7 +284,7 @@ static int decode_nul(struct bitstr *bs, const struct field_t *f,
>  static int decode_bool(struct bitstr *bs, const struct field_t *f,
>                         char *base, int level)
>  {
> -	PRINT("%*.s%s\n", level * TAB_SIZE, " ", f->name);
> +	PRINT("%*s%s\n", level * TAB_SIZE, " ", f->name);
>  
>  	INC_BIT(bs);
>  	if (nf_h323_error_boundary(bs, 0, 0))
> @@ -297,7 +297,7 @@ static int decode_oid(struct bitstr *bs, const struct field_t *f,
>  {
>  	int len;
>  
> -	PRINT("%*.s%s\n", level * TAB_SIZE, " ", f->name);
> +	PRINT("%*s%s\n", level * TAB_SIZE, " ", f->name);
>  
>  	BYTE_ALIGN(bs);
>  	if (nf_h323_error_boundary(bs, 1, 0))
> @@ -316,7 +316,7 @@ static int decode_int(struct bitstr *bs, const struct field_t *f,
>  {
>  	unsigned int len;
>  
> -	PRINT("%*.s%s", level * TAB_SIZE, " ", f->name);
> +	PRINT("%*s%s", level * TAB_SIZE, " ", f->name);
>  
>  	switch (f->sz) {
>  	case BYTE:		/* Range == 256 */
> @@ -363,7 +363,7 @@ static int decode_int(struct bitstr *bs, const struct field_t *f,
>  static int decode_enum(struct bitstr *bs, const struct field_t *f,
>                         char *base, int level)
>  {
> -	PRINT("%*.s%s\n", level * TAB_SIZE, " ", f->name);
> +	PRINT("%*s%s\n", level * TAB_SIZE, " ", f->name);
>  
>  	if ((f->attr & EXT) && get_bit(bs)) {
>  		INC_BITS(bs, 7);
> @@ -381,7 +381,7 @@ static int decode_bitstr(struct bitstr *bs, const struct field_t *f,
>  {
>  	unsigned int len;
>  
> -	PRINT("%*.s%s\n", level * TAB_SIZE, " ", f->name);
> +	PRINT("%*s%s\n", level * TAB_SIZE, " ", f->name);
>  
>  	BYTE_ALIGN(bs);
>  	switch (f->sz) {
> @@ -417,7 +417,7 @@ static int decode_numstr(struct bitstr *bs, const struct field_t *f,
>  {
>  	unsigned int len;
>  
> -	PRINT("%*.s%s\n", level * TAB_SIZE, " ", f->name);
> +	PRINT("%*s%s\n", level * TAB_SIZE, " ", f->name);
>  
>  	/* 2 <= Range <= 255 */
>  	if (nf_h323_error_boundary(bs, 0, f->sz))
> @@ -437,7 +437,7 @@ static int decode_octstr(struct bitstr *bs, const struct field_t *f,
>  {
>  	unsigned int len;
>  
> -	PRINT("%*.s%s", level * TAB_SIZE, " ", f->name);
> +	PRINT("%*s%s", level * TAB_SIZE, " ", f->name);
>  
>  	switch (f->sz) {
>  	case FIXD:		/* Range == 1 */
> @@ -490,7 +490,7 @@ static int decode_bmpstr(struct bitstr *bs, const struct field_t *f,
>  {
>  	unsigned int len;
>  
> -	PRINT("%*.s%s\n", level * TAB_SIZE, " ", f->name);
> +	PRINT("%*s%s\n", level * TAB_SIZE, " ", f->name);
>  
>  	switch (f->sz) {
>  	case BYTE:		/* Range == 256 */
> @@ -522,7 +522,7 @@ static int decode_seq(struct bitstr *bs, const struct field_t *f,
>  	const struct field_t *son;
>  	unsigned char *beg = NULL;
>  
> -	PRINT("%*.s%s\n", level * TAB_SIZE, " ", f->name);
> +	PRINT("%*s%s\n", level * TAB_SIZE, " ", f->name);
>  
>  	/* Decode? */
>  	base = (base && (f->attr & DECODE)) ? base + f->offset : NULL;
> @@ -544,7 +544,7 @@ static int decode_seq(struct bitstr *bs, const struct field_t *f,
>  	/* Decode the root components */
>  	for (i = opt = 0, son = f->fields; i < f->lb; i++, son++) {
>  		if (son->attr & STOP) {
> -			PRINT("%*.s%s\n", (level + 1) * TAB_SIZE, " ",
> +			PRINT("%*s%s\n", (level + 1) * TAB_SIZE, " ",
>  			      son->name);
>  			return H323_ERROR_STOP;
>  		}
> @@ -562,7 +562,7 @@ static int decode_seq(struct bitstr *bs, const struct field_t *f,
>  			if (nf_h323_error_boundary(bs, len, 0))
>  				return H323_ERROR_BOUND;
>  			if (!base || !(son->attr & DECODE)) {
> -				PRINT("%*.s%s\n", (level + 1) * TAB_SIZE,
> +				PRINT("%*s%s\n", (level + 1) * TAB_SIZE,
>  				      " ", son->name);
>  				bs->cur += len;
>  				continue;
> @@ -615,7 +615,7 @@ static int decode_seq(struct bitstr *bs, const struct field_t *f,
>  		}
>  
>  		if (son->attr & STOP) {
> -			PRINT("%*.s%s\n", (level + 1) * TAB_SIZE, " ",
> +			PRINT("%*s%s\n", (level + 1) * TAB_SIZE, " ",
>  			      son->name);
>  			return H323_ERROR_STOP;
>  		}
> @@ -629,7 +629,7 @@ static int decode_seq(struct bitstr *bs, const struct field_t *f,
>  		if (nf_h323_error_boundary(bs, len, 0))
>  			return H323_ERROR_BOUND;
>  		if (!base || !(son->attr & DECODE)) {
> -			PRINT("%*.s%s\n", (level + 1) * TAB_SIZE, " ",
> +			PRINT("%*s%s\n", (level + 1) * TAB_SIZE, " ",
>  			      son->name);
>  			bs->cur += len;
>  			continue;
> @@ -655,7 +655,7 @@ static int decode_seqof(struct bitstr *bs, const struct field_t *f,
>  	const struct field_t *son;
>  	unsigned char *beg = NULL;
>  
> -	PRINT("%*.s%s\n", level * TAB_SIZE, " ", f->name);
> +	PRINT("%*s%s\n", level * TAB_SIZE, " ", f->name);
>  
>  	/* Decode? */
>  	base = (base && (f->attr & DECODE)) ? base + f->offset : NULL;
> @@ -710,7 +710,7 @@ static int decode_seqof(struct bitstr *bs, const struct field_t *f,
>  			if (nf_h323_error_boundary(bs, len, 0))
>  				return H323_ERROR_BOUND;
>  			if (!base || !(son->attr & DECODE)) {
> -				PRINT("%*.s%s\n", (level + 1) * TAB_SIZE,
> +				PRINT("%*s%s\n", (level + 1) * TAB_SIZE,
>  				      " ", son->name);
>  				bs->cur += len;
>  				continue;
> @@ -751,7 +751,7 @@ static int decode_choice(struct bitstr *bs, const struct field_t *f,
>  	const struct field_t *son;
>  	unsigned char *beg = NULL;
>  
> -	PRINT("%*.s%s\n", level * TAB_SIZE, " ", f->name);
> +	PRINT("%*s%s\n", level * TAB_SIZE, " ", f->name);
>  
>  	/* Decode? */
>  	base = (base && (f->attr & DECODE)) ? base + f->offset : NULL;
> @@ -792,7 +792,7 @@ static int decode_choice(struct bitstr *bs, const struct field_t *f,
>  	/* Transfer to son level */
>  	son = &f->fields[type];
>  	if (son->attr & STOP) {
> -		PRINT("%*.s%s\n", (level + 1) * TAB_SIZE, " ", son->name);
> +		PRINT("%*s%s\n", (level + 1) * TAB_SIZE, " ", son->name);
>  		return H323_ERROR_STOP;
>  	}
>  
> @@ -804,7 +804,7 @@ static int decode_choice(struct bitstr *bs, const struct field_t *f,
>  		if (nf_h323_error_boundary(bs, len, 0))
>  			return H323_ERROR_BOUND;
>  		if (!base || !(son->attr & DECODE)) {
> -			PRINT("%*.s%s\n", (level + 1) * TAB_SIZE, " ",
> +			PRINT("%*s%s\n", (level + 1) * TAB_SIZE, " ",
>  			      son->name);
>  			bs->cur += len;
>  			return H323_ERROR_NONE;
> -- 
> 2.39.5
> 

^ permalink raw reply

* Re: [PATCH nf] netfilter: nf_conntrack_sip: fix use of uninitialized rtp_addr in process_sdp
From: Pablo Neira Ayuso @ 2026-03-26 20:21 UTC (permalink / raw)
  To: Weiming Shi
  Cc: Florian Westphal, David S . Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Phil Sutter, Simon Horman, netfilter-devel, coreteam,
	netdev, linux-kernel, Xiang Mei
In-Reply-To: <20260323080727.2932866-3-bestswngs@gmail.com>

On Mon, Mar 23, 2026 at 04:07:29PM +0800, Weiming Shi wrote:
> process_sdp() declares union nf_inet_addr rtp_addr on the stack and
> passes it to the nf_nat_sip sdp_session hook after walking the SDP
> media descriptions. However rtp_addr is only initialized inside the
> media loop when a recognized media type with a non-zero port is found.
> 
> If the SDP body contains no m= lines, only inactive media sections
> (m=audio 0 ...) or only unrecognized media types, rtp_addr is never
> assigned. Despite that, the function still calls hooks->sdp_session()
> with &rtp_addr, causing nf_nat_sdp_session() to format the stale stack
> value as an IP address and rewrite the SDP session owner and connection
> lines with it.
> 
> With CONFIG_INIT_STACK_ALL_ZERO (default on most distributions) this
> results in the session-level o= and c= addresses being rewritten to
> 0.0.0.0 for inactive SDP sessions. Without stack auto-init the
> rewritten address is whatever happened to be on the stack.
> 
> Fix this by pre-initializing rtp_addr from the session-level connection
> address (caddr) when available, and tracking via a have_rtp_addr flag
> whether any valid address was established. Skip the sdp_session hook
> entirely when no valid address exists.
> 
> Fixes: 4ab9e64e5e3c ("[NETFILTER]: nf_nat_sip: split up SDP mangling")
> Reported-by: Xiang Mei <xmei5@asu.edu>
> Signed-off-by: Weiming Shi <bestswngs@gmail.com>
> ---
>  net/netfilter/nf_conntrack_sip.c | 14 ++++++++++----
>  1 file changed, 10 insertions(+), 4 deletions(-)
> 
> diff --git a/net/netfilter/nf_conntrack_sip.c b/net/netfilter/nf_conntrack_sip.c
> index 4ab5ef71d96db..17af0ff4ea7ab 100644
> --- a/net/netfilter/nf_conntrack_sip.c
> +++ b/net/netfilter/nf_conntrack_sip.c
> @@ -1040,6 +1040,7 @@ static int process_sdp(struct sk_buff *skb, unsigned int protoff,
>  	unsigned int port;
>  	const struct sdp_media_type *t;
>  	int ret = NF_ACCEPT;
> +	bool have_rtp_addr = false;
>  
>  	hooks = rcu_dereference(nf_nat_sip_hooks);
>  
> @@ -1056,8 +1057,11 @@ static int process_sdp(struct sk_buff *skb, unsigned int protoff,
>  	caddr_len = 0;
>  	if (ct_sip_parse_sdp_addr(ct, *dptr, sdpoff, *datalen,
>  				  SDP_HDR_CONNECTION, SDP_HDR_MEDIA,
> -				  &matchoff, &matchlen, &caddr) > 0)
> +				  &matchoff, &matchlen, &caddr) > 0) {
>  		caddr_len = matchlen;
> +		memcpy(&rtp_addr, &caddr, sizeof(rtp_addr));
> +		have_rtp_addr = true;
> +	}
>  
>  	mediaoff = sdpoff;
>  	for (i = 0; i < ARRAY_SIZE(sdp_media_types); ) {
> @@ -1091,9 +1095,11 @@ static int process_sdp(struct sk_buff *skb, unsigned int protoff,
>  					  &matchoff, &matchlen, &maddr) > 0) {
>  			maddr_len = matchlen;
>  			memcpy(&rtp_addr, &maddr, sizeof(rtp_addr));
> -		} else if (caddr_len)
> +			have_rtp_addr = true;
> +		} else if (caddr_len) {
>  			memcpy(&rtp_addr, &caddr, sizeof(rtp_addr));
> -		else {
> +			have_rtp_addr = true;

After this update, this loop sets over rtp_addr, but this was already
set by ct_sip_parse_sdp_addr() a bit above.

This new chunk results in:

                } else if (caddr_len) {
                       memcpy(&rtp_addr, &caddr, sizeof(rtp_addr));
                       have_rtp_addr = true;

which is not needed? Why does caddr need to be copied over and over
again to rtp_addr?

> +		} else {
>  			nf_ct_helper_log(skb, ct, "cannot parse SDP message");
>  			return NF_DROP;
>  		}

^ permalink raw reply

* [PATCH next] wireguard: selftests: Remove spurious precision from banner printf
From: david.laight.linux @ 2026-03-26 20:18 UTC (permalink / raw)
  To: Jason A. Donenfeld, Shuah Khan, wireguard, netdev,
	linux-kselftest, linux-kernel
  Cc: David Laight, Masami Hiramatsu, Petr Mladek, Rasmus Villemoes,
	Andy Shevchenko, Steven Rostedt, Sergey Senozhatsky,
	Andrew Morton

From: David Laight <david.laight.linux@gmail.com>

Change ("%*.s", len, "") to ("%*s", len, "").
The former is equivalent to "%*.0s" but is really a typing mistake.
No actual change to the output.

Signed-off-by: David Laight <david.laight.linux@gmail.com>
---
 tools/testing/selftests/wireguard/qemu/init.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tools/testing/selftests/wireguard/qemu/init.c b/tools/testing/selftests/wireguard/qemu/init.c
index 3e49924dd77e..c87570494d30 100644
--- a/tools/testing/selftests/wireguard/qemu/init.c
+++ b/tools/testing/selftests/wireguard/qemu/init.c
@@ -53,7 +53,7 @@ static void print_banner(void)
 		panic("uname");
 
 	len = strlen("    WireGuard Test Suite on       ") + strlen(utsname.sysname) + strlen(utsname.release) + strlen(utsname.machine);
-	printf("\x1b[45m\x1b[33m\x1b[1m%*.s\x1b[0m\n\x1b[45m\x1b[33m\x1b[1m    WireGuard Test Suite on %s %s %s    \x1b[0m\n\x1b[45m\x1b[33m\x1b[1m%*.s\x1b[0m\n\n", len, "", utsname.sysname, utsname.release, utsname.machine, len, "");
+	printf("\x1b[45m\x1b[33m\x1b[1m%*s\x1b[0m\n\x1b[45m\x1b[33m\x1b[1m    WireGuard Test Suite on %s %s %s    \x1b[0m\n\x1b[45m\x1b[33m\x1b[1m%*s\x1b[0m\n\n", len, "", utsname.sysname, utsname.release, utsname.machine, len, "");
 }
 
 static void seed_rng(void)
-- 
2.39.5


^ permalink raw reply related

* [PATCH next] netfilter: nf_conntrack_h323: Correct indentation when H323_TRACE defined
From: david.laight.linux @ 2026-03-26 20:18 UTC (permalink / raw)
  To: Pablo Neira Ayuso, Florian Westphal, Phil Sutter, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
	netfilter-devel, coreteam, netdev, linux-kernel
  Cc: David Laight, Masami Hiramatsu, Petr Mladek, Rasmus Villemoes,
	Andy Shevchenko, Steven Rostedt, Sergey Senozhatsky,
	Andrew Morton

From: David Laight <david.laight.linux@gmail.com>

The trace lines are indented using PRINT("%*.s", xx, " ").
Userspace will treat this as "%*.0s" and will output no characters
when 'xx' is zero, the kernel treats it as "%*s" and will output
a single ' ' - which is probably what is intended.

Change all the formats to "%*s" removing the default precision.
This gives a single space indent when level is zero.

Signed-off-by: David Laight <david.laight.linux@gmail.com>
---
 net/netfilter/nf_conntrack_h323_asn1.c | 38 +++++++++++++-------------
 1 file changed, 19 insertions(+), 19 deletions(-)

diff --git a/net/netfilter/nf_conntrack_h323_asn1.c b/net/netfilter/nf_conntrack_h323_asn1.c
index 7b1497ed97d2..287402428975 100644
--- a/net/netfilter/nf_conntrack_h323_asn1.c
+++ b/net/netfilter/nf_conntrack_h323_asn1.c
@@ -276,7 +276,7 @@ static unsigned int get_uint(struct bitstr *bs, int b)
 static int decode_nul(struct bitstr *bs, const struct field_t *f,
                       char *base, int level)
 {
-	PRINT("%*.s%s\n", level * TAB_SIZE, " ", f->name);
+	PRINT("%*s%s\n", level * TAB_SIZE, " ", f->name);
 
 	return H323_ERROR_NONE;
 }
@@ -284,7 +284,7 @@ static int decode_nul(struct bitstr *bs, const struct field_t *f,
 static int decode_bool(struct bitstr *bs, const struct field_t *f,
                        char *base, int level)
 {
-	PRINT("%*.s%s\n", level * TAB_SIZE, " ", f->name);
+	PRINT("%*s%s\n", level * TAB_SIZE, " ", f->name);
 
 	INC_BIT(bs);
 	if (nf_h323_error_boundary(bs, 0, 0))
@@ -297,7 +297,7 @@ static int decode_oid(struct bitstr *bs, const struct field_t *f,
 {
 	int len;
 
-	PRINT("%*.s%s\n", level * TAB_SIZE, " ", f->name);
+	PRINT("%*s%s\n", level * TAB_SIZE, " ", f->name);
 
 	BYTE_ALIGN(bs);
 	if (nf_h323_error_boundary(bs, 1, 0))
@@ -316,7 +316,7 @@ static int decode_int(struct bitstr *bs, const struct field_t *f,
 {
 	unsigned int len;
 
-	PRINT("%*.s%s", level * TAB_SIZE, " ", f->name);
+	PRINT("%*s%s", level * TAB_SIZE, " ", f->name);
 
 	switch (f->sz) {
 	case BYTE:		/* Range == 256 */
@@ -363,7 +363,7 @@ static int decode_int(struct bitstr *bs, const struct field_t *f,
 static int decode_enum(struct bitstr *bs, const struct field_t *f,
                        char *base, int level)
 {
-	PRINT("%*.s%s\n", level * TAB_SIZE, " ", f->name);
+	PRINT("%*s%s\n", level * TAB_SIZE, " ", f->name);
 
 	if ((f->attr & EXT) && get_bit(bs)) {
 		INC_BITS(bs, 7);
@@ -381,7 +381,7 @@ static int decode_bitstr(struct bitstr *bs, const struct field_t *f,
 {
 	unsigned int len;
 
-	PRINT("%*.s%s\n", level * TAB_SIZE, " ", f->name);
+	PRINT("%*s%s\n", level * TAB_SIZE, " ", f->name);
 
 	BYTE_ALIGN(bs);
 	switch (f->sz) {
@@ -417,7 +417,7 @@ static int decode_numstr(struct bitstr *bs, const struct field_t *f,
 {
 	unsigned int len;
 
-	PRINT("%*.s%s\n", level * TAB_SIZE, " ", f->name);
+	PRINT("%*s%s\n", level * TAB_SIZE, " ", f->name);
 
 	/* 2 <= Range <= 255 */
 	if (nf_h323_error_boundary(bs, 0, f->sz))
@@ -437,7 +437,7 @@ static int decode_octstr(struct bitstr *bs, const struct field_t *f,
 {
 	unsigned int len;
 
-	PRINT("%*.s%s", level * TAB_SIZE, " ", f->name);
+	PRINT("%*s%s", level * TAB_SIZE, " ", f->name);
 
 	switch (f->sz) {
 	case FIXD:		/* Range == 1 */
@@ -490,7 +490,7 @@ static int decode_bmpstr(struct bitstr *bs, const struct field_t *f,
 {
 	unsigned int len;
 
-	PRINT("%*.s%s\n", level * TAB_SIZE, " ", f->name);
+	PRINT("%*s%s\n", level * TAB_SIZE, " ", f->name);
 
 	switch (f->sz) {
 	case BYTE:		/* Range == 256 */
@@ -522,7 +522,7 @@ static int decode_seq(struct bitstr *bs, const struct field_t *f,
 	const struct field_t *son;
 	unsigned char *beg = NULL;
 
-	PRINT("%*.s%s\n", level * TAB_SIZE, " ", f->name);
+	PRINT("%*s%s\n", level * TAB_SIZE, " ", f->name);
 
 	/* Decode? */
 	base = (base && (f->attr & DECODE)) ? base + f->offset : NULL;
@@ -544,7 +544,7 @@ static int decode_seq(struct bitstr *bs, const struct field_t *f,
 	/* Decode the root components */
 	for (i = opt = 0, son = f->fields; i < f->lb; i++, son++) {
 		if (son->attr & STOP) {
-			PRINT("%*.s%s\n", (level + 1) * TAB_SIZE, " ",
+			PRINT("%*s%s\n", (level + 1) * TAB_SIZE, " ",
 			      son->name);
 			return H323_ERROR_STOP;
 		}
@@ -562,7 +562,7 @@ static int decode_seq(struct bitstr *bs, const struct field_t *f,
 			if (nf_h323_error_boundary(bs, len, 0))
 				return H323_ERROR_BOUND;
 			if (!base || !(son->attr & DECODE)) {
-				PRINT("%*.s%s\n", (level + 1) * TAB_SIZE,
+				PRINT("%*s%s\n", (level + 1) * TAB_SIZE,
 				      " ", son->name);
 				bs->cur += len;
 				continue;
@@ -615,7 +615,7 @@ static int decode_seq(struct bitstr *bs, const struct field_t *f,
 		}
 
 		if (son->attr & STOP) {
-			PRINT("%*.s%s\n", (level + 1) * TAB_SIZE, " ",
+			PRINT("%*s%s\n", (level + 1) * TAB_SIZE, " ",
 			      son->name);
 			return H323_ERROR_STOP;
 		}
@@ -629,7 +629,7 @@ static int decode_seq(struct bitstr *bs, const struct field_t *f,
 		if (nf_h323_error_boundary(bs, len, 0))
 			return H323_ERROR_BOUND;
 		if (!base || !(son->attr & DECODE)) {
-			PRINT("%*.s%s\n", (level + 1) * TAB_SIZE, " ",
+			PRINT("%*s%s\n", (level + 1) * TAB_SIZE, " ",
 			      son->name);
 			bs->cur += len;
 			continue;
@@ -655,7 +655,7 @@ static int decode_seqof(struct bitstr *bs, const struct field_t *f,
 	const struct field_t *son;
 	unsigned char *beg = NULL;
 
-	PRINT("%*.s%s\n", level * TAB_SIZE, " ", f->name);
+	PRINT("%*s%s\n", level * TAB_SIZE, " ", f->name);
 
 	/* Decode? */
 	base = (base && (f->attr & DECODE)) ? base + f->offset : NULL;
@@ -710,7 +710,7 @@ static int decode_seqof(struct bitstr *bs, const struct field_t *f,
 			if (nf_h323_error_boundary(bs, len, 0))
 				return H323_ERROR_BOUND;
 			if (!base || !(son->attr & DECODE)) {
-				PRINT("%*.s%s\n", (level + 1) * TAB_SIZE,
+				PRINT("%*s%s\n", (level + 1) * TAB_SIZE,
 				      " ", son->name);
 				bs->cur += len;
 				continue;
@@ -751,7 +751,7 @@ static int decode_choice(struct bitstr *bs, const struct field_t *f,
 	const struct field_t *son;
 	unsigned char *beg = NULL;
 
-	PRINT("%*.s%s\n", level * TAB_SIZE, " ", f->name);
+	PRINT("%*s%s\n", level * TAB_SIZE, " ", f->name);
 
 	/* Decode? */
 	base = (base && (f->attr & DECODE)) ? base + f->offset : NULL;
@@ -792,7 +792,7 @@ static int decode_choice(struct bitstr *bs, const struct field_t *f,
 	/* Transfer to son level */
 	son = &f->fields[type];
 	if (son->attr & STOP) {
-		PRINT("%*.s%s\n", (level + 1) * TAB_SIZE, " ", son->name);
+		PRINT("%*s%s\n", (level + 1) * TAB_SIZE, " ", son->name);
 		return H323_ERROR_STOP;
 	}
 
@@ -804,7 +804,7 @@ static int decode_choice(struct bitstr *bs, const struct field_t *f,
 		if (nf_h323_error_boundary(bs, len, 0))
 			return H323_ERROR_BOUND;
 		if (!base || !(son->attr & DECODE)) {
-			PRINT("%*.s%s\n", (level + 1) * TAB_SIZE, " ",
+			PRINT("%*s%s\n", (level + 1) * TAB_SIZE, " ",
 			      son->name);
 			bs->cur += len;
 			return H323_ERROR_NONE;
-- 
2.39.5


^ permalink raw reply related

* Re: [PATCH] net: ipv6: ndisc: fix ndisc_ra_useropt to initialize nduseropt_padX fields to zero to prevent an info-leak
From: Simon Horman @ 2026-03-26 20:14 UTC (permalink / raw)
  To: Yochai Eisenrich; +Cc: David S. Miller, David Ahern, security, netdev
In-Reply-To: <20260324224925.2437775-1-echelonh@gmail.com>

On Wed, Mar 25, 2026 at 12:49:25AM +0200, Yochai Eisenrich wrote:
> When processing Router Advertisements with user options the kernel
> builds an RTM_NEWNDUSEROPT netlink message. The nduseroptmsg struct
> has three padding fields that are never zeroed and can leak kernel data
> 
> The fix is simple, just zeroes the padding fields.
> 
> Fixes: 31910575a9de ("[IPv6]: Export userland ND options through netlink
>     (RDNSS support)")

Fixes tags should be all on one line; no line wrapping.

> Signed-off-by: Yochai Eisenrich <echelonh@gmail.com>

Reviewed-by: Simon Horman <horms@kernel.org>

...

^ permalink raw reply

* Re: [PATCH net-next v2] net: mana: Use at least SZ_4K in doorbell ID range check
From: Simon Horman @ 2026-03-26 20:07 UTC (permalink / raw)
  To: Erni Sri Satya Vennela
  Cc: kys, haiyangz, wei.liu, decui, longli, andrew+netdev, davem,
	edumazet, kuba, pabeni, shradhagupta, kotaranov, dipayanroy,
	yury.norov, kees, linux-hyperv, netdev, linux-kernel
In-Reply-To: <20260325180423.1923060-1-ernis@linux.microsoft.com>

On Wed, Mar 25, 2026 at 11:04:17AM -0700, Erni Sri Satya Vennela wrote:
> mana_gd_ring_doorbell() accesses offsets up to DOORBELL_OFFSET_EQ
> (0xFF8) + 8 bytes = 4KB within each doorbell page. A db_page_size
> smaller than SZ_4K is fundamentally incompatible with the driver:
> doorbell pages would overlap and the device cannot function correctly.
> 
> Validate db_page_size at the source and fail the
> probe early if the value is below SZ_4K. This ensures the doorbell ID
> range check in mana_gd_register_device() can rely on db_page_size
> being valid.
> 
> Fixes: 89fe91c65992 ("net: mana: hardening: Validate doorbell ID from GDMA_REGISTER_DEVICE response")
> Signed-off-by: Erni Sri Satya Vennela <ernis@linux.microsoft.com>
> ---
> Changes in v2:
> * Remove "db_page_sz = max_t(u64, SZ_4K, gc->db_page_size)" in
>   mana_gd_register_device and validate db_page_sz at the source
>   mana_gf_init_pf_regs and mana_gd_init_vf_regs.
> * Update commit message.

Thanks for the update.

Reviewed-by: Simon Horman <horms@kernel.org>


^ permalink raw reply

* Re: [PATCH net-next v11 06/15] quic: add stream management
From: Jakub Kicinski @ 2026-03-26 20:07 UTC (permalink / raw)
  To: Xin Long
  Cc: network dev, quic, davem, Eric Dumazet, Paolo Abeni, Simon Horman,
	Stefan Metzmacher, Moritz Buhl, Tyler Fanelli, Pengtao He,
	Thomas Dreibholz, linux-cifs, Steve French, Namjae Jeon,
	Paulo Alcantara, Tom Talpey, kernel-tls-handshake, Chuck Lever,
	Jeff Layton, Steve Dickson, Hannes Reinecke, Alexander Aring,
	David Howells, Matthieu Baerts, John Ericson, Cong Wang,
	D . Wythe, Jason Baron, illiliti, Sabrina Dubroca,
	Marcelo Ricardo Leitner, Daniel Stenberg, Andy Gospodarek,
	Marc E . Fiuczynski
In-Reply-To: <CADvbK_eXUfnGGQgGc_k-ecCW3-Co1MpGb7UvMgLonPy3HRhyFA@mail.gmail.com>

On Thu, 26 Mar 2026 11:06:26 -0400 Xin Long wrote:
> The AI report on
> 
>   https://netdev-ai.bots.linux.dev/ai-review.html?id=1624d906-c0b6-4e12-a63f-5cbfc51b660e#patch-5
> 
> is false positive.
> 
> As the sk_alloc() calls sk_prot_alloc() with __GFP_ZERO, and the streams->head
> is always initialized to NULL.

Can you also check the google one?

https://sashiko.dev/#/patchset/4019cfba3ffd6f91aee24713d2adc778f858b4c4.1774410440.git.lucien.xin@gmail.com

^ permalink raw reply

* Re: [PATCH v3 net-next 02/14] dt-bindings: net: dsa: add NETC switch
From: Frank Li @ 2026-03-26 20:06 UTC (permalink / raw)
  To: Wei Fang
  Cc: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
	davem, edumazet, kuba, pabeni, robh, krzk+dt, conor+dt,
	f.fainelli, chleroy, horms, linux, andrew, netdev, linux-kernel,
	devicetree, linuxppc-dev, linux-arm-kernel, imx
In-Reply-To: <20260326062917.3552334-3-wei.fang@nxp.com>

On Thu, Mar 26, 2026 at 02:29:05PM +0800, Wei Fang wrote:
> Add bindings for NETC switch. This switch is a PCIe function of NETC IP,
> it supports advanced QoS with 8 traffic classes and 4 drop resilience
> levels, and a full range of  TSN standards capabilities. The switch CPU

Nit: double space before TSN.

> port connects to an internal ENETC port, which is also a PCIe function
> of NETC IP. So these two ports use a light-weight "pseudo MAC" instead
> of a back-to-back MAC, because the "pseudo MAC" provides the delineation
> between switch and ENETC, this translates to lower power (less logic and

what's means "this translates", do you means
"this help reduce power and latency."

> memory) and lower delay (as there is no serialization delay across this
> link).
>
> Signed-off-by: Wei Fang <wei.fang@nxp.com>
> ---
>  .../bindings/net/dsa/nxp,netc-switch.yaml     | 130 ++++++++++++++++++
>  1 file changed, 130 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/net/dsa/nxp,netc-switch.yaml
>
> diff --git a/Documentation/devicetree/bindings/net/dsa/nxp,netc-switch.yaml b/Documentation/devicetree/bindings/net/dsa/nxp,netc-switch.yaml
> new file mode 100644
> index 000000000000..e34a4e3504c3
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/net/dsa/nxp,netc-switch.yaml
> @@ -0,0 +1,130 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/net/dsa/nxp,netc-switch.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: NETC Switch family
> +
> +description:

Nit use ">" for multi paragraph.

others look good

Reviewed-by: Frank Li <Frank.Li@nxp.com>

> +  The NETC presents itself as a multi-function PCIe Root Complex Integrated
> +  Endpoint (RCiEP) and provides full 802.1Q Ethernet switch functionality,
> +  advanced QoS with 8 traffic classes and 4 drop resilience levels, and a
> +  full range of TSN standards capabilities.
> +
> +  The CPU port of the switch connects to an internal ENETC. The switch and
> +  the internal ENETC are fully integrated into the NETC IP, a back-to-back
> +  MAC is not required. Instead, a light-weight "pseudo MAC" provides the
> +  delineation between the switch and ENETC. This translates to lower power
> +  (less logic and memory) and lower delay (as there is no serialization
> +  delay across this link).
> +
> +maintainers:
> +  - Wei Fang <wei.fang@nxp.com>
> +
> +properties:
> +  compatible:
> +    enum:
> +      - pci1131,eef2
> +
> +  reg:
> +    maxItems: 1
> +
> +  dsa,member:
> +    description:
> +      The property indicates DSA cluster and switch index. For NETC switch,
> +      the valid range of the switch index is 1 ~ 7, the index is reflected
> +      in the switch tag as an indication of the switch ID where the frame
> +      originated. The value 0 is reserved for ENETC VEPA switch, whose ID
> +      is hardwired to zero.
> +
> +$ref: dsa.yaml#
> +
> +patternProperties:
> +  "^(ethernet-)?ports$":
> +    type: object
> +    additionalProperties: true
> +    patternProperties:
> +      "^(ethernet-)?port@[0-9a-f]$":
> +        type: object
> +
> +        $ref: dsa-port.yaml#
> +
> +        properties:
> +          clocks:
> +            items:
> +              - description: MAC transmit/receive reference clock.
> +
> +          clock-names:
> +            items:
> +              - const: ref
> +
> +          mdio:
> +            $ref: /schemas/net/mdio.yaml#
> +            unevaluatedProperties: false
> +            description:
> +              Optional child node for switch port, otherwise use NETC EMDIO.
> +
> +        unevaluatedProperties: false
> +
> +required:
> +  - compatible
> +  - reg
> +  - dsa,member
> +
> +allOf:
> +  - $ref: /schemas/pci/pci-device.yaml
> +
> +unevaluatedProperties: false
> +
> +examples:
> +  - |
> +    pcie {
> +        #address-cells = <3>;
> +        #size-cells = <2>;
> +
> +        ethernet-switch@0,2 {
> +            compatible = "pci1131,eef2";
> +            reg = <0x200 0 0 0 0>;
> +            dsa,member = <0 1>;
> +            pinctrl-names = "default";
> +            pinctrl-0 = <&pinctrl_switch>;
> +
> +            ports {
> +                #address-cells = <1>;
> +                #size-cells = <0>;
> +
> +                port@0 {
> +                    reg = <0>;
> +                    phy-handle = <&ethphy0>;
> +                    phy-mode = "mii";
> +                };
> +
> +                port@1 {
> +                    reg = <1>;
> +                    phy-handle = <&ethphy1>;
> +                    phy-mode = "mii";
> +                };
> +
> +                port@2 {
> +                    reg = <2>;
> +                    clocks = <&scmi_clk 103>;
> +                    clock-names = "ref";
> +                    phy-handle = <&ethphy2>;
> +                    phy-mode = "rgmii-id";
> +                };
> +
> +                port@3 {
> +                    reg = <3>;
> +                    ethernet = <&enetc3>;
> +                    phy-mode = "internal";
> +
> +                    fixed-link {
> +                        speed = <2500>;
> +                        full-duplex;
> +                        pause;
> +                    };
> +                };
> +            };
> +        };
> +    };
> --
> 2.34.1
>

^ permalink raw reply

* Re: [PATCH net] net: psp: check for device unregister when creating assoc
From: Jakub Kicinski @ 2026-03-26 20:04 UTC (permalink / raw)
  To: Willem de Bruijn
  Cc: davem, netdev, edumazet, pabeni, andrew+netdev, horms,
	Yiming Qian, daniel.zahka
In-Reply-To: <willemdebruijn.kernel.39ac32125c0cc@gmail.com>

On Tue, 24 Mar 2026 18:40:00 -0400 Willem de Bruijn wrote:
> > @@ -320,6 +320,11 @@ int psp_assoc_device_get_locked(const struct genl_split_ops *ops,
> >  	id = info->attrs[PSP_A_ASSOC_DEV_ID];
> >  	if (psd) {
> >  		mutex_lock(&psd->lock);
> > +		if (!psp_dev_is_registered(psd)) {
> > +			mutex_unlock(&psd->lock);
> > +			err = -ENODEV;
> > +			goto err_psd_put;
> > +		}  
> 
> This ensures that psd is valid for this caller of psp_dev_get_for_sock
> and psp_dev_tryget, which is its only one (for now).
> 
> But is it confusing that psd can be cleared out while a reference is
> held? Is the assumption that psp_dev_unregister usually holds the last
> reference and its psp_dev_put will complete the clean up by calling
> psp_dev_free. If so, would it make sense to defer everything
> to psp_dev_free?
> 
> This is a simpler changes and fixes the issue for the only caller, so
> LGTM. Just curious.

Meaning we should add a dance around removal like netdev does?
Sleep until all the references are gone?
Subjectively I think it's ugly, and in netdev it didn't really
prevent races. Dunno, definitely subjective.

^ permalink raw reply

* Re: [PATCH net] nfc: llcp: fix tlv offset wrap and missing bounds checks
From: Simon Horman @ 2026-03-26 20:01 UTC (permalink / raw)
  To: Oleh Konko
  Cc: netdev@vger.kernel.org, davem@davemloft.net, edumazet@google.com,
	kuba@kernel.org, pabeni@redhat.com, linux-kernel@vger.kernel.org,
	stable@vger.kernel.org
In-Reply-To: <463598db3dea48fc963e8431181ae68a.security@1.0.0.127.in-addr.arpa>

On Tue, Mar 24, 2026 at 09:25:41PM +0000, Oleh Konko wrote:
> nfc_llcp_parse_gb_tlv() and nfc_llcp_parse_connection_tlv() iterate a
> u16 tlv_array_len with a u8 offset. once cumulative TLV consumption
> crosses 255 bytes, offset wraps and the loop may continue past the
> declared TLV array bounds.
> 
> both parsers also read tlv[1] before checking that a full 2-byte TLV
> header remains, and they advance by length + 2 without validating that
> the declared payload still fits in the remaining array.
> 
> fix this by widening offset to u16 and by rejecting incomplete headers
> or truncated TLVs before dereferencing or advancing the cursor.

This patch seems to fix two problems.  It's usually best to fix one problem
per patch, creating a patch-set with two or more patches and a cover letter
if necessary.

> 
> Fixes: d646960f7986 ("NFC: Initial LLCP support")
> Cc: stable@vger.kernel.org
> Reported-by: Oleh Konko <security@1seal.org>
> Signed-off-by: Oleh Konko <security@1seal.org>

This patch seems to have been sent twice in about half an hour.
Please don't do that.

Rather, please observe a pause of 24h between sending updated
revisions of a patch.

Link: https://docs.kernel.org/process/maintainer-netdev.html

> ---
>  net/nfc/llcp_commands.c | 18 ++++++++++++++++--
>  1 file changed, 16 insertions(+), 2 deletions(-)
> 
> diff --git a/net/nfc/llcp_commands.c b/net/nfc/llcp_commands.c
> index 291f26fac..157afd62f 100644
> --- a/net/nfc/llcp_commands.c
> +++ b/net/nfc/llcp_commands.c
> @@ -193,7 +193,8 @@ int nfc_llcp_parse_gb_tlv(struct nfc_llcp_local *local,
>  			  const u8 *tlv_array, u16 tlv_array_len)
>  {
>  	const u8 *tlv = tlv_array;
> -	u8 type, length, offset = 0;
> +	u8 type, length;
> +	u16 offset = 0;

It seems to me that the value passed to this function by the caller,
nfc_llcp_set_remote_gb(), is limited to a maximum value of 255 - 3 by
virtue of being a u8 with 3 subtracted. So I'm not sure that offset
can overflow here.

For consistency with other parts of this call-chain
it might be worth changing the type of tlv_array_len to u8.
But that would be a clean-up for net-next rather than a fix for net.

>  
>  	pr_debug("TLV array length %d\n", tlv_array_len);
>  
> @@ -201,6 +202,9 @@ int nfc_llcp_parse_gb_tlv(struct nfc_llcp_local *local,
>  		return -ENODEV;
>  
>  	while (offset < tlv_array_len) {
> +		if (tlv_array_len - offset < 2)
> +			return -EINVAL;
> +
>  		type = tlv[0];
>  		length = tlv[1];
>  
> @@ -227,6 +231,9 @@ int nfc_llcp_parse_gb_tlv(struct nfc_llcp_local *local,
>  			break;
>  		}
>  
> +		if (tlv_array_len - offset < (u16)length + 2)
> +			return -EINVAL;
> +
>  		offset += length + 2;
>  		tlv += length + 2;
>  	}
> @@ -243,7 +250,8 @@ int nfc_llcp_parse_connection_tlv(struct nfc_llcp_sock *sock,
>  				  const u8 *tlv_array, u16 tlv_array_len)
>  {
>  	const u8 *tlv = tlv_array;
> -	u8 type, length, offset = 0;
> +	u8 type, length;
> +	u16 offset = 0;

In this case the callers pass skb->ken as tlv_array_len.
As skb->len is an unsigned int in theory it might overflow
the u16 used for tlv_array_len. But perhaps in practice that
doesn't occur?

In any case, if you are going to update the type of offset, then
I would just change the type of type, length and offset to unsigned int.
I don't see a value in using narrower types here.

>  
>  	pr_debug("TLV array length %d\n", tlv_array_len);
>  
> @@ -251,6 +259,9 @@ int nfc_llcp_parse_connection_tlv(struct nfc_llcp_sock *sock,
>  		return -ENOTCONN;
>  
>  	while (offset < tlv_array_len) {
> +		if (tlv_array_len - offset < 2)
> +			return -EINVAL;
> +
>  		type = tlv[0];
>  		length = tlv[1];
>  
> @@ -270,6 +281,9 @@ int nfc_llcp_parse_connection_tlv(struct nfc_llcp_sock *sock,
>  			break;
>  		}
>  
> +		if (tlv_array_len - offset < (u16)length + 2)
> +			return -EINVAL;
> +
>  		offset += length + 2;
>  		tlv += length + 2;
>  	}
> -- 
> 2.50.0
> 

^ permalink raw reply

* Re: [PATCH v2] net: phy: air_en8811h: add AN8811HB MCU assert/deassert support
From: Eric Woudstra @ 2026-03-26 20:00 UTC (permalink / raw)
  To: Lucien.Jheng, andrew, hkallweit1, linux, davem, edumazet, kuba,
	pabeni, netdev, linux-kernel, bjorn
  Cc: frank-w, daniel, lucien.jheng
In-Reply-To: <20260326153518.8387-1-lucienzx159@gmail.com>



On 3/26/26 4:35 PM, Lucien.Jheng wrote:
> AN8811HB needs a MCU soft-reset cycle before firmware loading begins.
> Assert the MCU (hold it in reset) and immediately deassert (release)
> via a dedicated PBUS register pair (0x5cf9f8 / 0x5cf9fc), accessed
> through the PHY-addr+8 MDIO bus node rather than the BUCKPBUS indirect
> path.  This clears the MCU state before the firmware loading sequence
> starts.

Perhapse you can register this extra device in the probe() function:

struct mdio_device *mdiodev;

mdiodev = mdio_device_create(phydev->mdio.bus,
			     phydev->mdio.addr + EN8811H_PBUS_ADDR_OFFS);
if (IS_ERR(mdiodev))
	return PTR_ERR(mdiodev);

ret = mdio_device_register(mdiodev);
if (ret) {
	mdio_device_free(mdiodev);
	return ret;
}

priv->pbusdev = mdiodev;

In the define part add:

#define EN8811H_PBUS_ADDR_OFFS	8

Add to struct en8811h_priv:

struct mdio_device	*pbusdev;

And in the remove function:

mdio_device_remove(priv->pbusdev);
mdio_device_free(priv->pbusdev);

Then you can use the device:

ret = en8811hb_some_neat_function(priv->pbusdev);

> 
> Add __air_pbus_reg_write() as a low-level helper for this access, then
> implement an8811hb_mcu_assert() / _deassert() on top of it.  Wire both
> into an8811hb_load_firmware() and en8811h_restart_mcu() so every
> firmware load or MCU restart on AN8811HB correctly sequences the reset
> control registers.
> 
> Fixes: 0a55766b7711 ("net: phy: air_en8811h: add Airoha AN8811HB support")
> Signed-off-by: Lucien Jheng <lucienzx159@gmail.com>
> ---
> Changes in v2:
> - Rewrite commit message: The previous wording was ambiguous,
>   as it suggested the MCU remains asserted during the entire
>   firmware loading process, rather than a quick reset cycle
>   before it starts.
>   Clarify that assert and deassert is an immediate soft-reset
>   cycle to clear MCU state before firmware loading begins.
> - Add Fixes: 0a55766b7711 ("net: phy: air_en8811h: add Airoha AN8811HB
>   support").
> - Change phydev_info() to phydev_dbg() in an8811hb_mcu_assert() and
>   an8811hb_mcu_deassert() to avoid noise during normal boot.
> 
>  drivers/net/phy/air_en8811h.c | 105 ++++++++++++++++++++++++++++++++++
>  1 file changed, 105 insertions(+)
> 
> diff --git a/drivers/net/phy/air_en8811h.c b/drivers/net/phy/air_en8811h.c
> index 29ae73e65caa..01fce1b93618 100644
> --- a/drivers/net/phy/air_en8811h.c
> +++ b/drivers/net/phy/air_en8811h.c
> @@ -170,6 +170,16 @@
>  #define   AN8811HB_CLK_DRV_CKO_LDPWD		BIT(13)
>  #define   AN8811HB_CLK_DRV_CKO_LPPWD		BIT(14)
> 
> +#define AN8811HB_MCU_SW_RST		0x5cf9f8
> +#define   AN8811HB_MCU_SW_RST_HOLD		BIT(16)
> +#define   AN8811HB_MCU_SW_RST_RUN		(BIT(16) | BIT(0))
> +#define AN8811HB_MCU_SW_START		0x5cf9fc
> +#define   AN8811HB_MCU_SW_START_EN		BIT(16)
> +
> +/* MII register constants for PBUS access (PHY addr + 8) */
> +#define AIR_PBUS_ADDR_HIGH		0x1c
> +#define AIR_PBUS_DATA_HIGH		0x10
> +
>  /* Led definitions */
>  #define EN8811H_LED_COUNT	3
> 
> @@ -254,6 +264,36 @@ static int air_phy_write_page(struct phy_device *phydev, int page)
>  	return __phy_write(phydev, AIR_EXT_PAGE_ACCESS, page);
>  }
> 
> +static int __air_pbus_reg_write(struct phy_device *phydev,
> +				u32 pbus_reg, u32 pbus_data)
> +{
> +	struct mii_bus *bus = phydev->mdio.bus;
> +	int pbus_addr = phydev->mdio.addr + 8;
> +	int ret;
> +
> +	ret = __mdiobus_write(bus, pbus_addr, AIR_EXT_PAGE_ACCESS,
> +			      upper_16_bits(pbus_reg));
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = __mdiobus_write(bus, pbus_addr, AIR_PBUS_ADDR_HIGH,
> +			      (pbus_reg & GENMASK(15, 6)) >> 6);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = __mdiobus_write(bus, pbus_addr, (pbus_reg & GENMASK(5, 2)) >> 2,
> +			      lower_16_bits(pbus_data));
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = __mdiobus_write(bus, pbus_addr, AIR_PBUS_DATA_HIGH,
> +			      upper_16_bits(pbus_data));
> +	if (ret < 0)
> +		return ret;
> +
> +	return 0;
> +}
> +
>  static int __air_buckpbus_reg_write(struct phy_device *phydev,
>  				    u32 pbus_address, u32 pbus_data)
>  {
> @@ -570,10 +610,65 @@ static int an8811hb_load_file(struct phy_device *phydev, const char *name,
>  	return ret;
>  }
> 
> +static int an8811hb_mcu_assert(struct phy_device *phydev)
> +{
> +	int ret;
> +
> +	phy_lock_mdio_bus(phydev);
> +
> +	ret = __air_pbus_reg_write(phydev, AN8811HB_MCU_SW_RST,
> +				   AN8811HB_MCU_SW_RST_HOLD);
> +	if (ret < 0)
> +		goto unlock;
> +
> +	ret = __air_pbus_reg_write(phydev, AN8811HB_MCU_SW_START, 0);
> +	if (ret < 0)
> +		goto unlock;
> +
> +	msleep(50);
> +	phydev_dbg(phydev, "MCU asserted\n");
> +
> +unlock:
> +	phy_unlock_mdio_bus(phydev);
> +	return ret;
> +}
> +
> +static int an8811hb_mcu_deassert(struct phy_device *phydev)
> +{
> +	int ret;
> +
> +	phy_lock_mdio_bus(phydev);
> +
> +	ret = __air_pbus_reg_write(phydev, AN8811HB_MCU_SW_START,
> +				   AN8811HB_MCU_SW_START_EN);
> +	if (ret < 0)
> +		goto unlock;
> +
> +	ret = __air_pbus_reg_write(phydev, AN8811HB_MCU_SW_RST,
> +				   AN8811HB_MCU_SW_RST_RUN);
> +	if (ret < 0)
> +		goto unlock;
> +
> +	msleep(50);
> +	phydev_dbg(phydev, "MCU deasserted\n");
> +
> +unlock:
> +	phy_unlock_mdio_bus(phydev);
> +	return ret;
> +}
> +
>  static int an8811hb_load_firmware(struct phy_device *phydev)
>  {
>  	int ret;
> 
> +	ret = an8811hb_mcu_assert(phydev);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = an8811hb_mcu_deassert(phydev);
> +	if (ret < 0)
> +		return ret;
> +
>  	ret = air_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1,
>  				     EN8811H_FW_CTRL_1_START);
>  	if (ret < 0)
> @@ -662,6 +757,16 @@ static int en8811h_restart_mcu(struct phy_device *phydev)
>  {
>  	int ret;
> 
> +	if (phy_id_compare_model(phydev->phy_id, AN8811HB_PHY_ID)) {
> +		ret = an8811hb_mcu_assert(phydev);
> +		if (ret < 0)
> +			return ret;
> +
> +		ret = an8811hb_mcu_deassert(phydev);
> +		if (ret < 0)
> +			return ret;
> +	}
> +
>  	ret = air_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1,
>  				     EN8811H_FW_CTRL_1_START);
>  	if (ret < 0)
> --
> 2.34.1
> 


^ permalink raw reply

* Re: [PATCH net 2/2] net: xilinx: axienet: Fix BQL accounting for multi-BD TX packets
From: Sean Anderson @ 2026-03-26 19:56 UTC (permalink / raw)
  To: Gupta, Suraj, andrew+netdev@lunn.ch, davem@davemloft.net,
	edumazet@google.com, kuba@kernel.org, pabeni@redhat.com,
	Simek, Michal, Pandey, Radhey Shyam, horms@kernel.org
  Cc: netdev@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
	linux-kernel@vger.kernel.org, Katakam, Harini
In-Reply-To: <SA1PR12MB679810BDE0FD91B702A869C2C956A@SA1PR12MB6798.namprd12.prod.outlook.com>

On 3/26/26 15:51, Gupta, Suraj wrote:
> [Public]
> 
>> -----Original Message-----
>> From: Sean Anderson <sean.anderson@linux.dev>
>> Sent: Thursday, March 26, 2026 9:08 PM
>> To: Gupta, Suraj <Suraj.Gupta2@amd.com>; andrew+netdev@lunn.ch;
>> davem@davemloft.net; edumazet@google.com; kuba@kernel.org;
>> pabeni@redhat.com; Simek, Michal <michal.simek@amd.com>; Pandey,
>> Radhey Shyam <radhey.shyam.pandey@amd.com>; horms@kernel.org
>> Cc: netdev@vger.kernel.org; linux-arm-kernel@lists.infradead.org; linux-
>> kernel@vger.kernel.org; Katakam, Harini <harini.katakam@amd.com>
>> Subject: Re: [PATCH net 2/2] net: xilinx: axienet: Fix BQL accounting for multi-BD
>> TX packets
>>
>> Caution: This message originated from an External Source. Use proper caution
>> when opening attachments, clicking links, or responding.
>>
>>
>> On 3/25/26 01:30, Gupta, Suraj wrote:
>> > [Public]
>> >
>> >> -----Original Message-----
>> >> From: Sean Anderson <sean.anderson@linux.dev>
>> >> Sent: Tuesday, March 24, 2026 9:39 PM
>> >> To: Gupta, Suraj <Suraj.Gupta2@amd.com>; andrew+netdev@lunn.ch;
>> >> davem@davemloft.net; edumazet@google.com; kuba@kernel.org;
>> >> pabeni@redhat.com; Simek, Michal <michal.simek@amd.com>; Pandey,
>> >> Radhey Shyam <radhey.shyam.pandey@amd.com>; horms@kernel.org
>> >> Cc: netdev@vger.kernel.org; linux-arm-kernel@lists.infradead.org;
>> >> linux- kernel@vger.kernel.org; Katakam, Harini
>> >> <harini.katakam@amd.com>
>> >> Subject: Re: [PATCH net 2/2] net: xilinx: axienet: Fix BQL accounting
>> >> for multi-BD TX packets
>> >>
>> >> Caution: This message originated from an External Source. Use proper
>> >> caution when opening attachments, clicking links, or responding.
>> >>
>> >>
>> >> On 3/24/26 10:53, Suraj Gupta wrote:
>> >> > When a TX packet spans multiple buffer descriptors
>> >> > (scatter-gather), the per-BD byte count is accumulated into a local
>> >> > variable that resets 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.
>> >> >
>> >> > Fix this by replacing the local accumulator with a persistent
>> >> > counter
>> >> > (tx_compl_bytes) that survives across polls and is reset only after
>> >> > updating BQL and stats.
>> >>
>> >> Do we need this? Can't we just do something like
>> >>
>> >
>> > Nope, the 'size' variable passed to axienet_free_tx_chain() is local
>> > to axienet_tx_poll() and goes out of scope between different polls.
>> > This means it can't track completion bytes across multiple NAPI polls.
>>
>> Yes, but that's fine since we only update completed bytes when we retire at
>> least one packet:
>>
>>         packets = axienet_free_tx_chain(lp, &size, budget);
>>         if (packets) {
>>                 netdev_completed_queue(ndev, packets, size);
>>                 u64_stats_update_begin(&lp->tx_stat_sync);
>>                 u64_stats_add(&lp->tx_packets, packets);
>>                 u64_stats_add(&lp->tx_bytes, size);
>>                 u64_stats_update_end(&lp->tx_stat_sync);
>>
>>                 /* Matches barrier in axienet_start_xmit */
>>                 smp_mb();
>>                 if (((tail - ci) & (lp->rx_bd_num - 1)) >= MAX_SKB_FRAGS + 1)
>>                         netif_wake_queue(ndev);
>>         }
>>
>> and this matches the value we use when enqueuing a packet
>>
>>         tail_p = lp->tx_bd_p + sizeof(*lp->tx_bd_v) * (last_p - lp->tx_bd_v);
>>         smp_store_release(&lp->tx_bd_tail, new_tail_ptr);
>>         netdev_sent_queue(ndev, skb->len);
>>
> 
> Let’s say we have a packet with 4 fragments. When xmit() is called, the last buffer descriptor (the 4th one) is marked with the retiral flag[1]. Now, during completion, suppose the first 3 buffer descriptors are processed in one NAPI poll, and the 4th descriptor is handled in a subsequent poll. In this scenario, the size won’t get updated for the first 3 BDs because there’s no packet retiral for them. Then, during the second poll, the size update will only reflect the completion length of the 4th BD.

That's why I suggested using skb->len and not the size from the descriptor.

--Sean

> This means that the bytes corresponding to the first 3 fragments are never credited to BQL, which leads to incorrect accounting and, eventually, to the TX queue stalling as described.
> Proposed changes to accumulate the completion length across polls until the entire packet is processed directly addresses this gap.
> 
> [1]: https://elixir.bootlin.com/linux/v7.0-rc5/source/drivers/net/ethernet/xilinx/xilinx_axienet_main.c#L1127
> 
> 
> Regards,
> Suraj
> 
>> > Regards,
>> > Suraj
>> >
>> >> diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c
>> >> b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c
>> >> index 415e9bc252527..1ea8a6592bce1 100644
>> >> --- a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c
>> >> +++ b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c
>> >> @@ -768,6 +768,7 @@ static int axienet_free_tx_chain(struct
>> >> axienet_local *lp, u32 *sizep, int budge
>> >>                 if (cur_p->skb) {
>> >>                         struct axienet_cb *cb = (void
>> >> *)cur_p->skb->cb;
>> >>
>> >> +                       *sizep += skb->len;
>> >>                         dma_unmap_sgtable(lp->dev, &cb->sgt, DMA_TO_DEVICE, 0);
>> >>                         sg_free_table_chained(&cb->sgt, XAE_INLINE_SG_CNT);
>> >>                         napi_consume_skb(cur_p->skb, budget); @@
>> >> -783,8 +784,6 @@ static int axienet_free_tx_chain(struct axienet_local *lp,
>> u32 *sizep, int budge
>> >>                 wmb();
>> >>                 cur_p->cntrl = 0;
>> >>                 cur_p->status = 0;
>> >> -
>> >> -               *sizep += status & XAXIDMA_BD_STS_ACTUAL_LEN_MASK;
>> >>         }
>> >>
>> >>         smp_store_release(&lp->tx_bd_ci, (ci + i) & (lp->tx_bd_num -
>> >> 1));
>> >>
>> >> > Fixes: c900e49d58eb ("net: xilinx: axienet: Implement BQL")
>> >> > Signed-off-by: Suraj Gupta <suraj.gupta2@amd.com>
>> >> > ---
>> >> >  drivers/net/ethernet/xilinx/xilinx_axienet.h  |  3 +++
>> >> > .../net/ethernet/xilinx/xilinx_axienet_main.c | 20
>> >> > +++++++++----------
>> >> >  2 files changed, 13 insertions(+), 10 deletions(-)
>> >> >
>> >> > diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet.h
>> >> > b/drivers/net/ethernet/xilinx/xilinx_axienet.h
>> >> > index 602389843342..a4444c939451 100644
>> >> > --- a/drivers/net/ethernet/xilinx/xilinx_axienet.h
>> >> > +++ b/drivers/net/ethernet/xilinx/xilinx_axienet.h
>> >> > @@ -509,6 +509,8 @@ struct skbuf_dma_descriptor {
>> >> >   *           complete. Only updated at runtime by TX NAPI poll.
>> >> >   * @tx_bd_tail:      Stores the index of the next Tx buffer descriptor in the
>> ring
>> >> >   *              to be populated.
>> >> > + * @tx_compl_bytes: Accumulates TX completion length until a full
>> packet is
>> >> > + *              reported to the stack.
>> >> >   * @tx_packets: TX packet count for statistics
>> >> >   * @tx_bytes:        TX byte count for statistics
>> >> >   * @tx_stat_sync: Synchronization object for TX stats @@ -592,6
>> >> > +594,7 @@ struct axienet_local {
>> >> >       u32 tx_bd_num;
>> >> >       u32 tx_bd_ci;
>> >> >       u32 tx_bd_tail;
>> >> > +     u32 tx_compl_bytes;
>> >> >       u64_stats_t tx_packets;
>> >> >       u64_stats_t tx_bytes;
>> >> >       struct u64_stats_sync tx_stat_sync; diff --git
>> >> > a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c
>> >> > b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c
>> >> > index b06e4c37ff61..95bf61986cb7 100644
>> >> > --- a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c
>> >> > +++ b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c
>> >> > @@ -692,6 +692,8 @@ static void axienet_dma_stop(struct
>> >> > axienet_local
>> >> *lp)
>> >> >       axienet_lock_mii(lp);
>> >> >       __axienet_device_reset(lp);
>> >> >       axienet_unlock_mii(lp);
>> >> > +
>> >> > +     lp->tx_compl_bytes = 0;
>> >> >  }
>> >> >
>> >> >  /**
>> >> > @@ -770,8 +772,6 @@ 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.
>> >> >   * @budget:  NAPI budget (use 0 when not called from NAPI poll)
>> >> >   *
>> >> >   * Would either be called after a successful transmit operation,
>> >> > or after @@ -780,7 +780,7 @@ static int axienet_device_reset(struct
>> >> > net_device
>> >> *ndev)
>> >> >   * Return: The number of packets handled.
>> >> >   */
>> >> >  static int axienet_free_tx_chain(struct axienet_local *lp, u32 first_bd,
>> >> > -                              int nr_bds, bool force, u32 *sizep, int budget)
>> >> > +                              int nr_bds, bool force, int budget)
>> >> >  {
>> >> >       struct axidma_bd *cur_p;
>> >> >       unsigned int status;
>> >> > @@ -819,8 +819,8 @@ static int axienet_free_tx_chain(struct
>> >> > axienet_local
>> >> *lp, u32 first_bd,
>> >> >               cur_p->cntrl = 0;
>> >> >               cur_p->status = 0;
>> >> >
>> >> > -             if (sizep)
>> >> > -                     *sizep += status & XAXIDMA_BD_STS_ACTUAL_LEN_MASK;
>> >> > +             if (!force)
>> >> > +                     lp->tx_compl_bytes += status &
>> >> > + XAXIDMA_BD_STS_ACTUAL_LEN_MASK;
>> >> >       }
>> >> >
>> >> >       if (!force) {
>> >> > @@ -999,18 +999,18 @@ static int axienet_tx_poll(struct napi_struct
>> >> > *napi, int budget)  {
>> >> >       struct axienet_local *lp = container_of(napi, struct axienet_local,
>> napi_tx);
>> >> >       struct net_device *ndev = lp->ndev;
>> >> > -     u32 size = 0;
>> >> >       int packets;
>> >> >
>> >> >       packets = axienet_free_tx_chain(lp, lp->tx_bd_ci, lp->tx_bd_num, false,
>> >> > -                                     &size, budget);
>> >> > +                                     budget);
>> >> >
>> >> >       if (packets) {
>> >> > -             netdev_completed_queue(ndev, packets, size);
>> >> > +             netdev_completed_queue(ndev, packets,
>> >> > + lp->tx_compl_bytes);
>> >> >               u64_stats_update_begin(&lp->tx_stat_sync);
>> >> >               u64_stats_add(&lp->tx_packets, packets);
>> >> > -             u64_stats_add(&lp->tx_bytes, size);
>> >> > +             u64_stats_add(&lp->tx_bytes, lp->tx_compl_bytes);
>> >> >               u64_stats_update_end(&lp->tx_stat_sync);
>> >> > +             lp->tx_compl_bytes = 0;
>> >> >
>> >> >               /* Matches barrier in axienet_start_xmit */
>> >> >               smp_mb();
>> >> > @@ -1115,7 +1115,7 @@ axienet_start_xmit(struct sk_buff *skb,
>> >> > struct
>> >> net_device *ndev)
>> >> >                               netdev_err(ndev, "TX DMA mapping error\n");
>> >> >                       ndev->stats.tx_dropped++;
>> >> >                       axienet_free_tx_chain(lp, orig_tail_ptr, ii + 1,
>> >> > -                                           true, NULL, 0);
>> >> > +                                           true, 0);
>> >> >                       dev_kfree_skb_any(skb);
>> >> >                       return NETDEV_TX_OK;
>> >> >               }


^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox