* [PATCH net-next v9 3/4] dsa: tag_mxl862xx: set dsa_default_offload_fwd_mark()
From: Daniel Golle @ 2026-04-01 13:34 UTC (permalink / raw)
To: Daniel Golle, Andrew Lunn, Vladimir Oltean, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, David Yang,
Simon Horman, Russell King, netdev, linux-kernel
Cc: Frank Wunderlich, Chad Monroe, Cezary Wilmanski, Liang Xu,
Benny (Ying-Tsan) Weng, Jose Maria Verdu Munoz, Avinash Jayaraman,
John Crispin
In-Reply-To: <cover.1775049897.git.daniel@makrotopia.org>
The MxL862xx offloads bridge forwarding in hardware, so set
dsa_default_offload_fwd_mark() to avoid duplicate forwarding of
packets of (eg. flooded) frames arriving at the CPU port.
Link-local frames are directly trapped to the CPU port only, so don't
set dsa_default_offload_fwd_mark() on those.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
v9: no changes
v8: no changes
v7: no changes
v6: no changes
v5: move link-local check before dsa_strip_etype_header()
v4: no changes
v3: no changes
v2: don't set dsa_default_offload_fwd_mark() on link-local frames
---
net/dsa/tag_mxl862xx.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/net/dsa/tag_mxl862xx.c b/net/dsa/tag_mxl862xx.c
index 01f2158682718..8daefeb8d49df 100644
--- a/net/dsa/tag_mxl862xx.c
+++ b/net/dsa/tag_mxl862xx.c
@@ -86,6 +86,9 @@ static struct sk_buff *mxl862_tag_rcv(struct sk_buff *skb,
return NULL;
}
+ if (likely(!is_link_local_ether_addr(eth_hdr(skb)->h_dest)))
+ dsa_default_offload_fwd_mark(skb);
+
/* remove the MxL862xx special tag between the MAC addresses and the
* current ethertype field.
*/
--
2.53.0
^ permalink raw reply related
* [PATCH net-next v9 4/4] net: dsa: mxl862xx: implement bridge offloading
From: Daniel Golle @ 2026-04-01 13:35 UTC (permalink / raw)
To: Daniel Golle, Andrew Lunn, Vladimir Oltean, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, David Yang,
Simon Horman, Russell King, netdev, linux-kernel
Cc: Frank Wunderlich, Chad Monroe, Cezary Wilmanski, Liang Xu,
Benny (Ying-Tsan) Weng, Jose Maria Verdu Munoz, Avinash Jayaraman,
John Crispin
In-Reply-To: <cover.1775049897.git.daniel@makrotopia.org>
Implement joining and leaving bridges as well as add, delete and dump
operations on isolated FDBs, port MDB membership management, and
setting a port's STP state.
The switch supports a maximum of 63 bridges, however, up to 12 may
be used as "single-port bridges" to isolate standalone ports.
Allowing up to 48 bridges to be offloaded seems more than enough on
that hardware, hence that is set as max_num_bridges.
A total of 128 bridge ports are supported in the bridge portmap, and
virtual bridge ports have to be used eg. for link-aggregation, hence
potentially exceeding the number of hardware ports.
The firmware-assigned bridge identifier (FID) for each offloaded bridge
is stored in an array used to map DSA bridge num to firmware bridge ID,
avoiding the need for a driver-private bridge tracking structure.
Bridge member portmaps are rebuilt on join/leave using
dsa_switch_for_each_bridge_member().
As there are now more users of the BRIDGEPORT_CONFIG_SET API and the
state of each port is cached locally, introduce a helper function
mxl862xx_set_bridge_port(struct dsa_switch *ds, int port) which
applies the cached per-port state to hardware. For standalone user
ports (dp->bridge == NULL), it additionally resets the port to
single-port bridge state: CPU-only portmap, learning and flooding
disabled. The CPU port path sets its state explicitly before calling
this helper and is therefore not affected by the reset.
Note that MASK_VLAN_BASED_MAC_LEARNING is intentionally absent from
the firmware write mask. After mxl862xx_reset(), the firmware
initialises all VLAN-based MAC learning fields to 0 (disabled), so
SVL is the active mode by default without having to set it explicitly.
Note that there is no convenient way to control flooding on per-port
level, so the driver is using a 0-rate QoS meter setup as a stopper in
lack of any better option. In order to be perfect the firmware-enforced
minimum bucket size is bypassed by directly writing 0s to the relevant
registers -- without that at least one 64-byte packet could still
pass before the meter would change from 'yellow' into 'red' state.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
v9:
* get rid of cached portmap, DSA bridge membership should be the
only source of truth
* always apply host-flood setting to single-port bridge (do not
loose state)
* cancel any pending host_flood_work in probe error path
* fix missing p->learning reset when reverting port to standalone
on bridge leave
* remove mxl862xx_add_single_port_bridge(); fold bridge FID
allocation and mxl862xx_bridge_config_fwd() call into port_setup()
directly, and move standalone port state reset (portmap, learning,
flood_block) into mxl862xx_set_bridge_port() for the !dp->bridge
user-port path
* refactor mxl862xx_allocate_bridge() to return the allocated FID
directly, with a negative value indicating error
* document in commit message that MASK_VLAN_BASED_MAC_LEARNING is
intentionally absent as SVL is the firmware's reset default
* add comment noting that a FID leaked on port_setup() failure is
tolerable as the port transitions to unused and the pool accounts
for this
v8:
* add missing fields to kernel-doc for struct mxl862xx_priv
* const'ify some function parameters
v7:
* use simple array to track firmware bridge to dsa bridge mapping
* use prototype of existing dsa_bridge_ports() helper moved from
yt921x.c to dsa.h which takes struct net_device * instead of
struct dsa_bridge * parameter.
* use new dsa_switch_for_each_bridge_member helper
* fix bridge allocation resource leak in case of
mxl862xx_sync_bridge_members() failing for the first port to join a
bridge
* zero-initialize struct mxl862xx_cfg in mxl862xx_set_ageing_time
* set initial=1 to always reset cursor at start when interating over
FDB in .fdb_dump
* hack zero-rate meter to truly always block
* use asynchronous worker for port_set_host_flood which runs from
atomic context and hence cannot sleep
* reorder function to minimize diffstat of planned follow-up
commits
v6:
* eliminate struct mxl862xx_bridge and driver-private bridge list,
store firmware bridge FID in new dsa_bridge->priv instead
* rework sync_bridge_members() to use dsa_bridge_for_each_member()
instead of for_each_set_bit() on driver-private portmap
* rework port_bridge_join/leave to use dsa_bridge.priv for first-
member detection and dsa_bridge_ports() for empty-bridge check
* derive active FID from dp->bridge->priv in set_bridge_port()
* simplify allocate_bridge()/free_bridge() to take struct dsa_bridge
pointer directly, drop kzalloc/kfree/list management
* simplify get_fid() to read db.bridge.priv directly
* remove mxl862xx_find_bridge()
* remove unnecessary default bridge config call in setup()
v5:
* introduce port_map helpers
* properly implement port_mdb_{add,del} operations
v4:
* add missing cpu_to_le32 in mxl862xx_bridge_config_fwd()
* use little-endian 32-bit type for (unused) age_timer API field
* better comment in port_set_host_flood() documenting architectural
limitation
* fix typo in comment "matche" should be "matches"
* few whitespace fixes
v3:
* refactor .port_bridge_join and .port_bridge_leave as requested
by Vladimir Oltean
* include linux/etherdevice.h which was missing and causing build
to fail (it accidentally slipped into a follow-up patch)
* remove left-over manual reset of learning state for port leaving
bridge
* remove unnecessary call to mxl862xx_port_fast_age() for port
leaving bridge
* add kernel-doc comments in mxl862xx.h instead of sporadic inline
comments covering only some of the struct members
* some other minor cosmetics (linebreaks, whitespace) here and there
v2:
* fix kernel-doc comments in API header
* use bitfield helpers for compound tci field in fdb API
* add missing endian conversion for mxl862xx_stp_port_cfg.port_state
as well as mxl862xx_mac_table_read.tci (spotted by AI review)
* drop manually resetting port learning state on bridge<->standalone
transitions, DSA framework takes care of that
* don't abort updating bridge ports on error, return error at the end
* report error in mxl862xx_port_bridge_leave()
* create mxl862xx_get_fid() helper and use it in
mxl862xx_port_fdb_add() and mxl862xx_port_fdb_del()
* propagete error of callback function in mxl862xx_port_fdb_dump()
* manually mxl862xx_port_fast_age() in mxl862xx_port_stp_state_set()
to avoid FDB poisoning due to race condition
---
drivers/net/dsa/mxl862xx/mxl862xx-api.h | 225 +++++++-
drivers/net/dsa/mxl862xx/mxl862xx-cmd.h | 20 +-
drivers/net/dsa/mxl862xx/mxl862xx.c | 736 ++++++++++++++++++++++--
drivers/net/dsa/mxl862xx/mxl862xx.h | 99 ++++
4 files changed, 1022 insertions(+), 58 deletions(-)
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-api.h b/drivers/net/dsa/mxl862xx/mxl862xx-api.h
index a9f599dbca25b..8677763544d78 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx-api.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-api.h
@@ -3,6 +3,7 @@
#ifndef __MXL862XX_API_H
#define __MXL862XX_API_H
+#include <linux/bits.h>
#include <linux/if_ether.h>
/**
@@ -34,6 +35,168 @@ struct mxl862xx_register_mod {
__le16 mask;
} __packed;
+/**
+ * enum mxl862xx_mac_table_filter - Source/Destination MAC address filtering
+ *
+ * @MXL862XX_MAC_FILTER_NONE: no filter
+ * @MXL862XX_MAC_FILTER_SRC: source address filter
+ * @MXL862XX_MAC_FILTER_DEST: destination address filter
+ * @MXL862XX_MAC_FILTER_BOTH: both source and destination filter
+ */
+enum mxl862xx_mac_table_filter {
+ MXL862XX_MAC_FILTER_NONE = 0,
+ MXL862XX_MAC_FILTER_SRC = BIT(0),
+ MXL862XX_MAC_FILTER_DEST = BIT(1),
+ MXL862XX_MAC_FILTER_BOTH = BIT(0) | BIT(1),
+};
+
+#define MXL862XX_TCI_VLAN_ID GENMASK(11, 0)
+#define MXL862XX_TCI_VLAN_CFI_DEI BIT(12)
+#define MXL862XX_TCI_VLAN_PRI GENMASK(15, 13)
+
+/* Set in port_id to use port_map[] as a portmap bitmap instead of a single
+ * port ID. When clear, port_id selects one port; when set, the firmware
+ * ignores the lower bits of port_id and writes port_map[] directly into
+ * the PCE bridge port map.
+ */
+#define MXL862XX_PORTMAP_FLAG BIT(31)
+
+/**
+ * struct mxl862xx_mac_table_add - MAC Table Entry to be added
+ * @fid: Filtering Identifier (FID) (not supported by all switches)
+ * @port_id: Ethernet Port number
+ * @port_map: Bridge Port Map
+ * @sub_if_id: Sub-Interface Identifier Destination
+ * @age_timer: Aging Time in seconds
+ * @vlan_id: STAG VLAN Id
+ * @static_entry: Static Entry (value will be aged out if not set to static)
+ * @traffic_class: Egress queue traffic class
+ * @mac: MAC Address to add to the table
+ * @filter_flag: See &enum mxl862xx_mac_table_filter
+ * @igmp_controlled: Packet is marked as IGMP controlled if destination MAC
+ * address matches MAC in this entry
+ * @associated_mac: Associated Mac address
+ * @tci: TCI for B-Step
+ * Bit [0:11] - VLAN ID
+ * Bit [12] - VLAN CFI/DEI
+ * Bit [13:15] - VLAN PRI
+ */
+struct mxl862xx_mac_table_add {
+ __le16 fid;
+ __le32 port_id;
+ __le16 port_map[8];
+ __le16 sub_if_id;
+ __le32 age_timer;
+ __le16 vlan_id;
+ u8 static_entry;
+ u8 traffic_class;
+ u8 mac[ETH_ALEN];
+ u8 filter_flag;
+ u8 igmp_controlled;
+ u8 associated_mac[ETH_ALEN];
+ __le16 tci;
+} __packed;
+
+/**
+ * struct mxl862xx_mac_table_remove - MAC Table Entry to be removed
+ * @fid: Filtering Identifier (FID)
+ * @mac: MAC Address to be removed from the table.
+ * @filter_flag: See &enum mxl862xx_mac_table_filter
+ * @tci: TCI for B-Step
+ * Bit [0:11] - VLAN ID
+ * Bit [12] - VLAN CFI/DEI
+ * Bit [13:15] - VLAN PRI
+ */
+struct mxl862xx_mac_table_remove {
+ __le16 fid;
+ u8 mac[ETH_ALEN];
+ u8 filter_flag;
+ __le16 tci;
+} __packed;
+
+/**
+ * struct mxl862xx_mac_table_read - MAC Table Entry to be read
+ * @initial: Restart the get operation from the beginning of the table
+ * @last: Indicates that the read operation returned last entry
+ * @fid: Get the MAC table entry belonging to the given Filtering Identifier
+ * @port_id: The Bridge Port ID
+ * @port_map: Bridge Port Map
+ * @age_timer: Aging Time
+ * @vlan_id: STAG VLAN Id
+ * @static_entry: Indicates if this is a Static Entry
+ * @sub_if_id: Sub-Interface Identifier Destination
+ * @mac: MAC Address. Filled out by the switch API implementation.
+ * @filter_flag: See &enum mxl862xx_mac_table_filter
+ * @igmp_controlled: Packet is marked as IGMP controlled if destination MAC
+ * address matches the MAC in this entry
+ * @entry_changed: Indicate if the Entry has Changed
+ * @associated_mac: Associated MAC address
+ * @hit_status: MAC Table Hit Status Update
+ * @tci: TCI for B-Step
+ * Bit [0:11] - VLAN ID
+ * Bit [12] - VLAN CFI/DEI
+ * Bit [13:15] - VLAN PRI
+ * @first_bridge_port_id: The port this MAC address has first been learned.
+ * This is used for loop detection.
+ */
+struct mxl862xx_mac_table_read {
+ u8 initial;
+ u8 last;
+ __le16 fid;
+ __le32 port_id;
+ __le16 port_map[8];
+ __le32 age_timer;
+ __le16 vlan_id;
+ u8 static_entry;
+ __le16 sub_if_id;
+ u8 mac[ETH_ALEN];
+ u8 filter_flag;
+ u8 igmp_controlled;
+ u8 entry_changed;
+ u8 associated_mac[ETH_ALEN];
+ u8 hit_status;
+ __le16 tci;
+ __le16 first_bridge_port_id;
+} __packed;
+
+/**
+ * struct mxl862xx_mac_table_query - MAC Table Entry key-based lookup
+ * @mac: MAC Address to search for (input)
+ * @fid: Filtering Identifier (input)
+ * @found: Set by firmware: 1 if entry was found, 0 if not
+ * @port_id: Bridge Port ID (output; MSB set if portmap mode)
+ * @port_map: Bridge Port Map (output; valid for static entries)
+ * @sub_if_id: Sub-Interface Identifier Destination
+ * @age_timer: Aging Time
+ * @vlan_id: STAG VLAN Id
+ * @static_entry: Indicates if this is a Static Entry
+ * @filter_flag: See &enum mxl862xx_mac_table_filter (input+output)
+ * @igmp_controlled: IGMP controlled flag
+ * @entry_changed: Entry changed flag
+ * @associated_mac: Associated MAC address
+ * @hit_status: MAC Table Hit Status Update
+ * @tci: TCI (VLAN ID + CFI/DEI + PRI) (input)
+ * @first_bridge_port_id: First learned bridge port
+ */
+struct mxl862xx_mac_table_query {
+ u8 mac[ETH_ALEN];
+ __le16 fid;
+ u8 found;
+ __le32 port_id;
+ __le16 port_map[8];
+ __le16 sub_if_id;
+ __le32 age_timer;
+ __le16 vlan_id;
+ u8 static_entry;
+ u8 filter_flag;
+ u8 igmp_controlled;
+ u8 entry_changed;
+ u8 associated_mac[ETH_ALEN];
+ u8 hit_status;
+ __le16 tci;
+ __le16 first_bridge_port_id;
+} __packed;
+
/**
* enum mxl862xx_mac_clear_type - MAC table clear type
* @MXL862XX_MAC_CLEAR_PHY_PORT: clear dynamic entries based on port_id
@@ -138,6 +301,40 @@ enum mxl862xx_bridge_port_egress_meter {
MXL862XX_BRIDGE_PORT_EGRESS_METER_MAX,
};
+/**
+ * struct mxl862xx_qos_meter_cfg - Rate meter configuration
+ * @enable: Enable/disable meter
+ * @meter_id: Meter ID (assigned by firmware on alloc)
+ * @meter_name: Meter name string
+ * @meter_type: Meter algorithm type (srTCM = 0, trTCM = 1)
+ * @cbs: Committed Burst Size (in bytes)
+ * @res1: Reserved
+ * @ebs: Excess Burst Size (in bytes)
+ * @res2: Reserved
+ * @rate: Committed Information Rate (in kbit/s)
+ * @pi_rate: Peak Information Rate (in kbit/s)
+ * @colour_blind_mode: Colour-blind mode enable
+ * @pkt_mode: Packet mode enable
+ * @local_overhd: Local overhead accounting enable
+ * @local_overhd_val: Local overhead accounting value
+ */
+struct mxl862xx_qos_meter_cfg {
+ u8 enable;
+ __le16 meter_id;
+ char meter_name[32];
+ __le32 meter_type;
+ __le32 cbs;
+ __le32 res1;
+ __le32 ebs;
+ __le32 res2;
+ __le32 rate;
+ __le32 pi_rate;
+ u8 colour_blind_mode;
+ u8 pkt_mode;
+ u8 local_overhd;
+ __le16 local_overhd_val;
+} __packed;
+
/**
* enum mxl862xx_bridge_forward_mode - Bridge forwarding type of packet
* @MXL862XX_BRIDGE_FORWARD_FLOOD: Packet is flooded to port members of
@@ -456,7 +653,7 @@ struct mxl862xx_pmapper {
*/
struct mxl862xx_bridge_port_config {
__le16 bridge_port_id;
- __le32 mask; /* enum mxl862xx_bridge_port_config_mask */
+ __le32 mask; /* enum mxl862xx_bridge_port_config_mask */
__le16 bridge_id;
u8 ingress_extended_vlan_enable;
__le16 ingress_extended_vlan_block_id;
@@ -658,6 +855,32 @@ struct mxl862xx_ctp_port_assignment {
__le16 bridge_port_id;
} __packed;
+/**
+ * enum mxl862xx_stp_port_state - Spanning Tree Protocol port states
+ * @MXL862XX_STP_PORT_STATE_FORWARD: Forwarding state
+ * @MXL862XX_STP_PORT_STATE_DISABLE: Disabled/Discarding state
+ * @MXL862XX_STP_PORT_STATE_LEARNING: Learning state
+ * @MXL862XX_STP_PORT_STATE_BLOCKING: Blocking/Listening
+ */
+enum mxl862xx_stp_port_state {
+ MXL862XX_STP_PORT_STATE_FORWARD = 0,
+ MXL862XX_STP_PORT_STATE_DISABLE,
+ MXL862XX_STP_PORT_STATE_LEARNING,
+ MXL862XX_STP_PORT_STATE_BLOCKING,
+};
+
+/**
+ * struct mxl862xx_stp_port_cfg - Configures the Spanning Tree Protocol state
+ * @port_id: Port number
+ * @fid: Filtering Identifier (FID)
+ * @port_state: See &enum mxl862xx_stp_port_state
+ */
+struct mxl862xx_stp_port_cfg {
+ __le16 port_id;
+ __le16 fid;
+ __le32 port_state; /* enum mxl862xx_stp_port_state */
+} __packed;
+
/**
* struct mxl862xx_sys_fw_image_version - Firmware version information
* @iv_major: firmware major version
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
index f6852ade64e7c..9f6c5bf9fdf21 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
@@ -15,12 +15,15 @@
#define MXL862XX_BRDG_MAGIC 0x300
#define MXL862XX_BRDGPORT_MAGIC 0x400
#define MXL862XX_CTP_MAGIC 0x500
+#define MXL862XX_QOS_MAGIC 0x600
#define MXL862XX_SWMAC_MAGIC 0xa00
+#define MXL862XX_STP_MAGIC 0xf00
#define MXL862XX_SS_MAGIC 0x1600
#define GPY_GPY2XX_MAGIC 0x1800
#define SYS_MISC_MAGIC 0x1900
#define MXL862XX_COMMON_CFGGET (MXL862XX_COMMON_MAGIC + 0x9)
+#define MXL862XX_COMMON_CFGSET (MXL862XX_COMMON_MAGIC + 0xa)
#define MXL862XX_COMMON_REGISTERMOD (MXL862XX_COMMON_MAGIC + 0x11)
#define MXL862XX_BRIDGE_ALLOC (MXL862XX_BRDG_MAGIC + 0x1)
@@ -35,14 +38,23 @@
#define MXL862XX_CTP_PORTASSIGNMENTSET (MXL862XX_CTP_MAGIC + 0x3)
+#define MXL862XX_QOS_METERCFGSET (MXL862XX_QOS_MAGIC + 0x2)
+#define MXL862XX_QOS_METERALLOC (MXL862XX_QOS_MAGIC + 0x2a)
+
+#define MXL862XX_MAC_TABLEENTRYADD (MXL862XX_SWMAC_MAGIC + 0x2)
+#define MXL862XX_MAC_TABLEENTRYREAD (MXL862XX_SWMAC_MAGIC + 0x3)
+#define MXL862XX_MAC_TABLEENTRYQUERY (MXL862XX_SWMAC_MAGIC + 0x4)
+#define MXL862XX_MAC_TABLEENTRYREMOVE (MXL862XX_SWMAC_MAGIC + 0x5)
#define MXL862XX_MAC_TABLECLEARCOND (MXL862XX_SWMAC_MAGIC + 0x8)
-#define MXL862XX_SS_SPTAG_SET (MXL862XX_SS_MAGIC + 0x02)
+#define MXL862XX_SS_SPTAG_SET (MXL862XX_SS_MAGIC + 0x2)
+
+#define MXL862XX_STP_PORTCFGSET (MXL862XX_STP_MAGIC + 0x2)
-#define INT_GPHY_READ (GPY_GPY2XX_MAGIC + 0x01)
-#define INT_GPHY_WRITE (GPY_GPY2XX_MAGIC + 0x02)
+#define INT_GPHY_READ (GPY_GPY2XX_MAGIC + 0x1)
+#define INT_GPHY_WRITE (GPY_GPY2XX_MAGIC + 0x2)
-#define SYS_MISC_FW_VERSION (SYS_MISC_MAGIC + 0x02)
+#define SYS_MISC_FW_VERSION (SYS_MISC_MAGIC + 0x2)
#define MMD_API_MAXIMUM_ID 0x7fff
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.c b/drivers/net/dsa/mxl862xx/mxl862xx.c
index af9b00b2d2c43..e4e16c7207630 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
@@ -7,8 +7,11 @@
* Copyright (C) 2025 Daniel Golle <daniel@makrotopia.org>
*/
-#include <linux/module.h>
+#include <linux/bitfield.h>
#include <linux/delay.h>
+#include <linux/etherdevice.h>
+#include <linux/if_bridge.h>
+#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/of_mdio.h>
#include <linux/phy.h>
@@ -36,6 +39,17 @@
#define MXL862XX_READY_TIMEOUT_MS 10000
#define MXL862XX_READY_POLL_MS 100
+#define MXL862XX_TCM_INST_SEL 0xe00
+#define MXL862XX_TCM_CBS 0xe12
+#define MXL862XX_TCM_EBS 0xe13
+
+static const int mxl862xx_flood_meters[] = {
+ MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_UC,
+ MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_MC_IP,
+ MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_MC_NON_IP,
+ MXL862XX_BRIDGE_PORT_EGRESS_METER_BROADCAST,
+};
+
static enum dsa_tag_protocol mxl862xx_get_tag_protocol(struct dsa_switch *ds,
int port,
enum dsa_tag_protocol m)
@@ -168,6 +182,199 @@ static int mxl862xx_setup_mdio(struct dsa_switch *ds)
return ret;
}
+static int mxl862xx_bridge_config_fwd(struct dsa_switch *ds, u16 bridge_id,
+ bool ucast_flood, bool mcast_flood,
+ bool bcast_flood)
+{
+ struct mxl862xx_bridge_config bridge_config = {};
+ struct mxl862xx_priv *priv = ds->priv;
+ int ret;
+
+ bridge_config.mask = cpu_to_le32(MXL862XX_BRIDGE_CONFIG_MASK_FORWARDING_MODE);
+ bridge_config.bridge_id = cpu_to_le16(bridge_id);
+
+ bridge_config.forward_unknown_unicast = cpu_to_le32(ucast_flood ?
+ MXL862XX_BRIDGE_FORWARD_FLOOD : MXL862XX_BRIDGE_FORWARD_DISCARD);
+
+ bridge_config.forward_unknown_multicast_ip = cpu_to_le32(mcast_flood ?
+ MXL862XX_BRIDGE_FORWARD_FLOOD : MXL862XX_BRIDGE_FORWARD_DISCARD);
+ bridge_config.forward_unknown_multicast_non_ip =
+ bridge_config.forward_unknown_multicast_ip;
+
+ bridge_config.forward_broadcast = cpu_to_le32(bcast_flood ?
+ MXL862XX_BRIDGE_FORWARD_FLOOD : MXL862XX_BRIDGE_FORWARD_DISCARD);
+
+ ret = MXL862XX_API_WRITE(priv, MXL862XX_BRIDGE_CONFIGSET, bridge_config);
+ if (ret)
+ dev_err(ds->dev, "failed to configure bridge %u forwarding: %d\n",
+ bridge_id, ret);
+
+ return ret;
+}
+
+/* Allocate a single zero-rate meter shared by all ports and flood types.
+ * All flood-blocking egress sub-meters point to this one meter so that any
+ * packet hitting this meter is unconditionally dropped.
+ *
+ * The firmware API requires CBS >= 64 (its bs2ls encoder clamps smaller
+ * values), so the meter is initially configured with CBS=EBS=64.
+ * A zero-rate bucket starts full at CBS bytes, which would let one packet
+ * through before the bucket empties. To eliminate this one-packet leak we
+ * override CBS and EBS to zero via direct register writes after the API call;
+ * the hardware accepts CBS=0 and immediately flags the bucket as exceeded,
+ * so no traffic can ever pass.
+ */
+static int mxl862xx_setup_drop_meter(struct dsa_switch *ds)
+{
+ struct mxl862xx_qos_meter_cfg meter = {};
+ struct mxl862xx_priv *priv = ds->priv;
+ struct mxl862xx_register_mod reg;
+ int ret;
+
+ /* meter_id=0 means auto-alloc */
+ ret = MXL862XX_API_READ(priv, MXL862XX_QOS_METERALLOC, meter);
+ if (ret)
+ return ret;
+
+ meter.enable = true;
+ meter.cbs = cpu_to_le32(64);
+ meter.ebs = cpu_to_le32(64);
+ snprintf(meter.meter_name, sizeof(meter.meter_name), "drop");
+
+ ret = MXL862XX_API_WRITE(priv, MXL862XX_QOS_METERCFGSET, meter);
+ if (ret)
+ return ret;
+
+ priv->drop_meter = le16_to_cpu(meter.meter_id);
+
+ /* Select the meter instance for subsequent TCM register access. */
+ reg.addr = cpu_to_le16(MXL862XX_TCM_INST_SEL);
+ reg.data = cpu_to_le16(priv->drop_meter);
+ reg.mask = cpu_to_le16(0xffff);
+ ret = MXL862XX_API_WRITE(priv, MXL862XX_COMMON_REGISTERMOD, reg);
+ if (ret)
+ return ret;
+
+ /* Zero CBS so the committed bucket starts empty (exceeded). */
+ reg.addr = cpu_to_le16(MXL862XX_TCM_CBS);
+ reg.data = 0;
+ ret = MXL862XX_API_WRITE(priv, MXL862XX_COMMON_REGISTERMOD, reg);
+ if (ret)
+ return ret;
+
+ /* Zero EBS so the excess bucket starts empty (exceeded). */
+ reg.addr = cpu_to_le16(MXL862XX_TCM_EBS);
+ return MXL862XX_API_WRITE(priv, MXL862XX_COMMON_REGISTERMOD, reg);
+}
+
+static int mxl862xx_set_bridge_port(struct dsa_switch *ds, int port)
+{
+ struct mxl862xx_bridge_port_config br_port_cfg = {};
+ struct dsa_port *dp = dsa_to_port(ds, port);
+ struct mxl862xx_priv *priv = ds->priv;
+ struct mxl862xx_port *p = &priv->ports[port];
+ struct dsa_port *member_dp;
+ u16 bridge_id;
+ bool enable;
+ int i, idx;
+
+ if (!p->setup_done)
+ return 0;
+
+ if (dsa_port_is_cpu(dp)) {
+ dsa_switch_for_each_user_port(member_dp, ds) {
+ if (member_dp->cpu_dp->index != port)
+ continue;
+ mxl862xx_fw_portmap_set_bit(br_port_cfg.bridge_port_map,
+ member_dp->index);
+ }
+ } else if (dp->bridge) {
+ dsa_switch_for_each_bridge_member(member_dp, ds,
+ dp->bridge->dev) {
+ if (member_dp->index == port)
+ continue;
+ mxl862xx_fw_portmap_set_bit(br_port_cfg.bridge_port_map,
+ member_dp->index);
+ }
+ mxl862xx_fw_portmap_set_bit(br_port_cfg.bridge_port_map,
+ dp->cpu_dp->index);
+ } else {
+ mxl862xx_fw_portmap_set_bit(br_port_cfg.bridge_port_map,
+ dp->cpu_dp->index);
+ p->flood_block = 0;
+ p->learning = false;
+ }
+
+ bridge_id = dp->bridge ? priv->bridges[dp->bridge->num] : p->fid;
+
+ br_port_cfg.bridge_port_id = cpu_to_le16(port);
+ br_port_cfg.bridge_id = cpu_to_le16(bridge_id);
+ br_port_cfg.mask = cpu_to_le32(MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_ID |
+ MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_PORT_MAP |
+ MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_SRC_MAC_LEARNING |
+ MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_SUB_METER);
+ br_port_cfg.src_mac_learning_disable = !p->learning;
+
+ for (i = 0; i < ARRAY_SIZE(mxl862xx_flood_meters); i++) {
+ idx = mxl862xx_flood_meters[i];
+ enable = !!(p->flood_block & BIT(idx));
+
+ br_port_cfg.egress_traffic_sub_meter_id[idx] =
+ enable ? cpu_to_le16(priv->drop_meter) : 0;
+ br_port_cfg.egress_sub_metering_enable[idx] = enable;
+ }
+
+ return MXL862XX_API_WRITE(priv, MXL862XX_BRIDGEPORT_CONFIGSET,
+ br_port_cfg);
+}
+
+static int mxl862xx_sync_bridge_members(struct dsa_switch *ds,
+ const struct dsa_bridge *bridge)
+{
+ struct dsa_port *dp;
+ int ret = 0, err;
+
+ dsa_switch_for_each_bridge_member(dp, ds, bridge->dev) {
+ err = mxl862xx_set_bridge_port(ds, dp->index);
+ if (err)
+ ret = err;
+ }
+
+ return ret;
+}
+
+static int mxl862xx_allocate_bridge(struct mxl862xx_priv *priv)
+{
+ struct mxl862xx_bridge_alloc br_alloc = {};
+ int ret;
+
+ ret = MXL862XX_API_READ(priv, MXL862XX_BRIDGE_ALLOC, br_alloc);
+ if (ret)
+ return ret;
+
+ return le16_to_cpu(br_alloc.bridge_id);
+}
+
+static void mxl862xx_free_bridge(struct dsa_switch *ds,
+ const struct dsa_bridge *bridge)
+{
+ struct mxl862xx_priv *priv = ds->priv;
+ u16 fw_id = priv->bridges[bridge->num];
+ struct mxl862xx_bridge_alloc br_alloc = {
+ .bridge_id = cpu_to_le16(fw_id),
+ };
+ int ret;
+
+ ret = MXL862XX_API_WRITE(priv, MXL862XX_BRIDGE_FREE, br_alloc);
+ if (ret) {
+ dev_err(ds->dev, "failed to free fw bridge %u: %pe\n",
+ fw_id, ERR_PTR(ret));
+ return;
+ }
+
+ priv->bridges[bridge->num] = 0;
+}
+
static int mxl862xx_setup(struct dsa_switch *ds)
{
struct mxl862xx_priv *priv = ds->priv;
@@ -181,6 +388,10 @@ static int mxl862xx_setup(struct dsa_switch *ds)
if (ret)
return ret;
+ ret = mxl862xx_setup_drop_meter(ds);
+ if (ret)
+ return ret;
+
return mxl862xx_setup_mdio(ds);
}
@@ -260,99 +471,137 @@ static int mxl862xx_configure_sp_tag_proto(struct dsa_switch *ds, int port,
static int mxl862xx_setup_cpu_bridge(struct dsa_switch *ds, int port)
{
- struct mxl862xx_bridge_port_config br_port_cfg = {};
struct mxl862xx_priv *priv = ds->priv;
- u16 bridge_port_map = 0;
- struct dsa_port *dp;
- /* CPU port bridge setup */
- br_port_cfg.mask = cpu_to_le32(MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_PORT_MAP |
- MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_SRC_MAC_LEARNING |
- MXL862XX_BRIDGE_PORT_CONFIG_MASK_VLAN_BASED_MAC_LEARNING);
+ priv->ports[port].fid = MXL862XX_DEFAULT_BRIDGE;
+ priv->ports[port].learning = true;
- br_port_cfg.bridge_port_id = cpu_to_le16(port);
- br_port_cfg.src_mac_learning_disable = false;
- br_port_cfg.vlan_src_mac_vid_enable = true;
- br_port_cfg.vlan_dst_mac_vid_enable = true;
+ return mxl862xx_set_bridge_port(ds, port);
+}
+
+static int mxl862xx_port_bridge_join(struct dsa_switch *ds, int port,
+ const struct dsa_bridge bridge,
+ bool *tx_fwd_offload,
+ struct netlink_ext_ack *extack)
+{
+ struct mxl862xx_priv *priv = ds->priv;
+ int ret;
+
+ if (!priv->bridges[bridge.num]) {
+ ret = mxl862xx_allocate_bridge(priv);
+ if (ret < 0)
+ return ret;
+
+ priv->bridges[bridge.num] = ret;
- /* include all assigned user ports in the CPU portmap */
- dsa_switch_for_each_user_port(dp, ds) {
- /* it's safe to rely on cpu_dp being valid for user ports */
- if (dp->cpu_dp->index != port)
- continue;
+ /* Free bridge here on error, DSA rollback won't. */
+ ret = mxl862xx_sync_bridge_members(ds, &bridge);
+ if (ret) {
+ mxl862xx_free_bridge(ds, &bridge);
+ return ret;
+ }
- bridge_port_map |= BIT(dp->index);
+ return 0;
}
- br_port_cfg.bridge_port_map[0] |= cpu_to_le16(bridge_port_map);
- return MXL862XX_API_WRITE(priv, MXL862XX_BRIDGEPORT_CONFIGSET, br_port_cfg);
+ return mxl862xx_sync_bridge_members(ds, &bridge);
}
-static int mxl862xx_add_single_port_bridge(struct dsa_switch *ds, int port)
+static void mxl862xx_port_bridge_leave(struct dsa_switch *ds, int port,
+ const struct dsa_bridge bridge)
{
- struct mxl862xx_bridge_port_config br_port_cfg = {};
- struct dsa_port *dp = dsa_to_port(ds, port);
- struct mxl862xx_bridge_alloc br_alloc = {};
- int ret;
+ int err;
- ret = MXL862XX_API_READ(ds->priv, MXL862XX_BRIDGE_ALLOC, br_alloc);
- if (ret) {
- dev_err(ds->dev, "failed to allocate a bridge for port %d\n", port);
- return ret;
- }
+ err = mxl862xx_sync_bridge_members(ds, &bridge);
+ if (err)
+ dev_err(ds->dev,
+ "failed to sync bridge members after port %d left: %pe\n",
+ port, ERR_PTR(err));
- br_port_cfg.bridge_id = br_alloc.bridge_id;
- br_port_cfg.bridge_port_id = cpu_to_le16(port);
- br_port_cfg.mask = cpu_to_le32(MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_ID |
- MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_PORT_MAP |
- MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_SRC_MAC_LEARNING |
- MXL862XX_BRIDGE_PORT_CONFIG_MASK_VLAN_BASED_MAC_LEARNING);
- br_port_cfg.src_mac_learning_disable = true;
- br_port_cfg.vlan_src_mac_vid_enable = false;
- br_port_cfg.vlan_dst_mac_vid_enable = false;
- /* As this function is only called for user ports it is safe to rely on
- * cpu_dp being valid
+ /* Revert leaving port, omitted by the sync above, to its
+ * single-port bridge
*/
- br_port_cfg.bridge_port_map[0] = cpu_to_le16(BIT(dp->cpu_dp->index));
+ err = mxl862xx_set_bridge_port(ds, port);
+ if (err)
+ dev_err(ds->dev,
+ "failed to update bridge port %d state: %pe\n", port,
+ ERR_PTR(err));
- return MXL862XX_API_WRITE(ds->priv, MXL862XX_BRIDGEPORT_CONFIGSET, br_port_cfg);
+ if (!dsa_bridge_ports(ds, bridge.dev))
+ mxl862xx_free_bridge(ds, &bridge);
}
static int mxl862xx_port_setup(struct dsa_switch *ds, int port)
{
+ struct mxl862xx_priv *priv = ds->priv;
struct dsa_port *dp = dsa_to_port(ds, port);
bool is_cpu_port = dsa_port_is_cpu(dp);
int ret;
- /* disable port and flush MAC entries */
ret = mxl862xx_port_state(ds, port, false);
if (ret)
return ret;
mxl862xx_port_fast_age(ds, port);
- /* skip setup for unused and DSA ports */
if (dsa_port_is_unused(dp) ||
dsa_port_is_dsa(dp))
return 0;
- /* configure tag protocol */
ret = mxl862xx_configure_sp_tag_proto(ds, port, is_cpu_port);
if (ret)
return ret;
- /* assign CTP port IDs */
ret = mxl862xx_configure_ctp_port(ds, port, port,
is_cpu_port ? 32 - port : 1);
if (ret)
return ret;
if (is_cpu_port)
- /* assign user ports to CPU port bridge */
return mxl862xx_setup_cpu_bridge(ds, port);
- /* setup single-port bridge for user ports */
- return mxl862xx_add_single_port_bridge(ds, port);
+ /* setup single-port bridge for user ports.
+ * If this fails, the FID is leaked -- but the port then transitions
+ * to unused, and the FID pool is sized to tolerate this.
+ */
+ ret = mxl862xx_allocate_bridge(priv);
+ if (ret < 0) {
+ dev_err(ds->dev, "failed to allocate a bridge for port %d\n", port);
+ return ret;
+ }
+ priv->ports[port].fid = ret;
+ /* Standalone ports should not flood unknown unicast or multicast
+ * towards the CPU by default; only broadcast is needed initially.
+ */
+ ret = mxl862xx_bridge_config_fwd(ds, priv->ports[port].fid,
+ false, false, true);
+ if (ret)
+ return ret;
+ ret = mxl862xx_set_bridge_port(ds, port);
+ if (ret)
+ return ret;
+
+ priv->ports[port].setup_done = true;
+
+ return 0;
+}
+
+static void mxl862xx_port_teardown(struct dsa_switch *ds, int port)
+{
+ struct mxl862xx_priv *priv = ds->priv;
+ struct dsa_port *dp = dsa_to_port(ds, port);
+
+ if (dsa_port_is_unused(dp) || dsa_port_is_dsa(dp))
+ return;
+
+ /* Prevent deferred host_flood_work from acting on stale state.
+ * The flag is checked under rtnl_lock() by the worker; since
+ * teardown also runs under RTNL, this is race-free.
+ *
+ * HW EVLAN/VF blocks are not freed here -- the firmware receives
+ * a full reset on the next probe, which reclaims all resources.
+ */
+ priv->ports[port].setup_done = false;
}
static void mxl862xx_phylink_get_caps(struct dsa_switch *ds, int port,
@@ -365,14 +614,371 @@ static void mxl862xx_phylink_get_caps(struct dsa_switch *ds, int port,
config->supported_interfaces);
}
+static int mxl862xx_get_fid(struct dsa_switch *ds, struct dsa_db db)
+{
+ struct mxl862xx_priv *priv = ds->priv;
+
+ switch (db.type) {
+ case DSA_DB_PORT:
+ return priv->ports[db.dp->index].fid;
+
+ case DSA_DB_BRIDGE:
+ if (!priv->bridges[db.bridge.num])
+ return -ENOENT;
+ return priv->bridges[db.bridge.num];
+
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int mxl862xx_port_fdb_add(struct dsa_switch *ds, int port,
+ const unsigned char *addr, u16 vid, struct dsa_db db)
+{
+ struct mxl862xx_mac_table_add param = {};
+ int fid = mxl862xx_get_fid(ds, db), ret;
+ struct mxl862xx_priv *priv = ds->priv;
+
+ if (fid < 0)
+ return fid;
+
+ param.port_id = cpu_to_le32(port);
+ param.static_entry = true;
+ param.fid = cpu_to_le16(fid);
+ param.tci = cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, vid));
+ ether_addr_copy(param.mac, addr);
+
+ ret = MXL862XX_API_WRITE(priv, MXL862XX_MAC_TABLEENTRYADD, param);
+ if (ret)
+ dev_err(ds->dev, "failed to add FDB entry on port %d\n", port);
+
+ return ret;
+}
+
+static int mxl862xx_port_fdb_del(struct dsa_switch *ds, int port,
+ const unsigned char *addr, u16 vid, const struct dsa_db db)
+{
+ struct mxl862xx_mac_table_remove param = {};
+ int fid = mxl862xx_get_fid(ds, db), ret;
+ struct mxl862xx_priv *priv = ds->priv;
+
+ if (fid < 0)
+ return fid;
+
+ param.fid = cpu_to_le16(fid);
+ param.tci = cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, vid));
+ ether_addr_copy(param.mac, addr);
+
+ ret = MXL862XX_API_WRITE(priv, MXL862XX_MAC_TABLEENTRYREMOVE, param);
+ if (ret)
+ dev_err(ds->dev, "failed to remove FDB entry on port %d\n", port);
+
+ return ret;
+}
+
+static int mxl862xx_port_fdb_dump(struct dsa_switch *ds, int port,
+ dsa_fdb_dump_cb_t *cb, void *data)
+{
+ struct mxl862xx_mac_table_read param = { .initial = 1 };
+ struct mxl862xx_priv *priv = ds->priv;
+ u32 entry_port_id;
+ int ret;
+
+ while (true) {
+ ret = MXL862XX_API_READ(priv, MXL862XX_MAC_TABLEENTRYREAD, param);
+ if (ret)
+ return ret;
+
+ if (param.last)
+ break;
+
+ entry_port_id = le32_to_cpu(param.port_id);
+
+ if (entry_port_id == port) {
+ ret = cb(param.mac, FIELD_GET(MXL862XX_TCI_VLAN_ID,
+ le16_to_cpu(param.tci)),
+ param.static_entry, data);
+ if (ret)
+ return ret;
+ }
+
+ memset(¶m, 0, sizeof(param));
+ }
+
+ return 0;
+}
+
+static int mxl862xx_port_mdb_add(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_mdb *mdb,
+ const struct dsa_db db)
+{
+ struct mxl862xx_mac_table_query qparam = {};
+ struct mxl862xx_mac_table_add aparam = {};
+ struct mxl862xx_priv *priv = ds->priv;
+ int fid, ret;
+
+ fid = mxl862xx_get_fid(ds, db);
+ if (fid < 0)
+ return fid;
+
+ ether_addr_copy(qparam.mac, mdb->addr);
+ qparam.fid = cpu_to_le16(fid);
+ qparam.tci = cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, mdb->vid));
+
+ ret = MXL862XX_API_READ(priv, MXL862XX_MAC_TABLEENTRYQUERY, qparam);
+ if (ret)
+ return ret;
+
+ /* Build the ADD command using portmap mode */
+ ether_addr_copy(aparam.mac, mdb->addr);
+ aparam.fid = cpu_to_le16(fid);
+ aparam.tci = cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, mdb->vid));
+ aparam.static_entry = true;
+ aparam.port_id = cpu_to_le32(MXL862XX_PORTMAP_FLAG);
+
+ if (qparam.found)
+ memcpy(aparam.port_map, qparam.port_map,
+ sizeof(aparam.port_map));
+
+ mxl862xx_fw_portmap_set_bit(aparam.port_map, port);
+
+ return MXL862XX_API_WRITE(priv, MXL862XX_MAC_TABLEENTRYADD, aparam);
+}
+
+static int mxl862xx_port_mdb_del(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_mdb *mdb,
+ const struct dsa_db db)
+{
+ struct mxl862xx_mac_table_remove rparam = {};
+ struct mxl862xx_mac_table_query qparam = {};
+ struct mxl862xx_mac_table_add aparam = {};
+ int fid = mxl862xx_get_fid(ds, db), ret;
+ struct mxl862xx_priv *priv = ds->priv;
+
+ if (fid < 0)
+ return fid;
+
+ qparam.fid = cpu_to_le16(fid);
+ qparam.tci = cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, mdb->vid));
+ ether_addr_copy(qparam.mac, mdb->addr);
+
+ ret = MXL862XX_API_READ(priv, MXL862XX_MAC_TABLEENTRYQUERY, qparam);
+ if (ret)
+ return ret;
+
+ if (!qparam.found)
+ return 0;
+
+ mxl862xx_fw_portmap_clear_bit(qparam.port_map, port);
+
+ if (mxl862xx_fw_portmap_is_empty(qparam.port_map)) {
+ rparam.fid = cpu_to_le16(fid);
+ rparam.tci = cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, mdb->vid));
+ ether_addr_copy(rparam.mac, mdb->addr);
+ ret = MXL862XX_API_WRITE(priv, MXL862XX_MAC_TABLEENTRYREMOVE, rparam);
+ } else {
+ /* Write back with reduced portmap */
+ aparam.fid = cpu_to_le16(fid);
+ aparam.tci = cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, mdb->vid));
+ ether_addr_copy(aparam.mac, mdb->addr);
+ aparam.static_entry = true;
+ aparam.port_id = cpu_to_le32(MXL862XX_PORTMAP_FLAG);
+ memcpy(aparam.port_map, qparam.port_map, sizeof(aparam.port_map));
+ ret = MXL862XX_API_WRITE(priv, MXL862XX_MAC_TABLEENTRYADD, aparam);
+ }
+
+ return ret;
+}
+
+static int mxl862xx_set_ageing_time(struct dsa_switch *ds, unsigned int msecs)
+{
+ struct mxl862xx_cfg param = {};
+ int ret;
+
+ ret = MXL862XX_API_READ(ds->priv, MXL862XX_COMMON_CFGGET, param);
+ if (ret) {
+ dev_err(ds->dev, "failed to read switch config\n");
+ return ret;
+ }
+
+ param.mac_table_age_timer = cpu_to_le32(MXL862XX_AGETIMER_CUSTOM);
+ param.age_timer = cpu_to_le32(msecs / 1000);
+ ret = MXL862XX_API_WRITE(ds->priv, MXL862XX_COMMON_CFGSET, param);
+ if (ret)
+ dev_err(ds->dev, "failed to set ageing\n");
+
+ return ret;
+}
+
+static void mxl862xx_port_stp_state_set(struct dsa_switch *ds, int port,
+ u8 state)
+{
+ struct mxl862xx_stp_port_cfg param = {
+ .port_id = cpu_to_le16(port),
+ };
+ struct mxl862xx_priv *priv = ds->priv;
+ int ret;
+
+ switch (state) {
+ case BR_STATE_DISABLED:
+ param.port_state = cpu_to_le32(MXL862XX_STP_PORT_STATE_DISABLE);
+ break;
+ case BR_STATE_BLOCKING:
+ case BR_STATE_LISTENING:
+ param.port_state = cpu_to_le32(MXL862XX_STP_PORT_STATE_BLOCKING);
+ break;
+ case BR_STATE_LEARNING:
+ param.port_state = cpu_to_le32(MXL862XX_STP_PORT_STATE_LEARNING);
+ break;
+ case BR_STATE_FORWARDING:
+ param.port_state = cpu_to_le32(MXL862XX_STP_PORT_STATE_FORWARD);
+ break;
+ default:
+ dev_err(ds->dev, "invalid STP state: %d\n", state);
+ return;
+ }
+
+ ret = MXL862XX_API_WRITE(priv, MXL862XX_STP_PORTCFGSET, param);
+ if (ret) {
+ dev_err(ds->dev, "failed to set STP state on port %d\n", port);
+ return;
+ }
+
+ /* The firmware may re-enable MAC learning as a side-effect of entering
+ * LEARNING or FORWARDING state (per 802.1D defaults).
+ * Re-apply the driver's intended learning and metering config so that
+ * standalone ports keep learning disabled.
+ */
+ ret = mxl862xx_set_bridge_port(ds, port);
+ if (ret)
+ dev_err(ds->dev, "failed to reapply brport flags on port %d\n",
+ port);
+
+ mxl862xx_port_fast_age(ds, port);
+}
+
+/* Deferred work handler for host flood configuration.
+ *
+ * port_set_host_flood is called from atomic context (under
+ * netif_addr_lock), so firmware calls must be deferred. The worker
+ * acquires rtnl_lock() to serialize with DSA callbacks that access the
+ * same driver state.
+ */
+static void mxl862xx_host_flood_work_fn(struct work_struct *work)
+{
+ struct mxl862xx_port *p = container_of(work, struct mxl862xx_port,
+ host_flood_work);
+ struct mxl862xx_priv *priv = p->priv;
+ struct dsa_switch *ds = priv->ds;
+
+ rtnl_lock();
+
+ /* Port may have been torn down between scheduling and now. */
+ if (!p->setup_done) {
+ rtnl_unlock();
+ return;
+ }
+
+ /* Always write to the standalone FID. When standalone it takes effect
+ * immediately; when bridged the port uses the shared bridge FID so the
+ * write is a no-op for current forwarding, but the state is preserved
+ * in hardware and is ready once the port returns to standalone.
+ */
+ mxl862xx_bridge_config_fwd(ds, p->fid, p->host_flood_uc,
+ p->host_flood_mc, true);
+
+ rtnl_unlock();
+}
+
+static void mxl862xx_port_set_host_flood(struct dsa_switch *ds, int port,
+ bool uc, bool mc)
+{
+ struct mxl862xx_priv *priv = ds->priv;
+ struct mxl862xx_port *p = &priv->ports[port];
+
+ p->host_flood_uc = uc;
+ p->host_flood_mc = mc;
+ schedule_work(&p->host_flood_work);
+}
+
+static int mxl862xx_port_pre_bridge_flags(struct dsa_switch *ds, int port,
+ const struct switchdev_brport_flags flags,
+ struct netlink_ext_ack *extack)
+{
+ if (flags.mask & ~(BR_FLOOD | BR_MCAST_FLOOD | BR_BCAST_FLOOD |
+ BR_LEARNING))
+ return -EINVAL;
+
+ return 0;
+}
+
+static int mxl862xx_port_bridge_flags(struct dsa_switch *ds, int port,
+ const struct switchdev_brport_flags flags,
+ struct netlink_ext_ack *extack)
+{
+ struct mxl862xx_priv *priv = ds->priv;
+ unsigned long old_block = priv->ports[port].flood_block;
+ unsigned long block = old_block;
+ int ret;
+
+ if (flags.mask & BR_FLOOD) {
+ if (flags.val & BR_FLOOD)
+ block &= ~BIT(MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_UC);
+ else
+ block |= BIT(MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_UC);
+ }
+
+ if (flags.mask & BR_MCAST_FLOOD) {
+ if (flags.val & BR_MCAST_FLOOD) {
+ block &= ~BIT(MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_MC_IP);
+ block &= ~BIT(MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_MC_NON_IP);
+ } else {
+ block |= BIT(MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_MC_IP);
+ block |= BIT(MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_MC_NON_IP);
+ }
+ }
+
+ if (flags.mask & BR_BCAST_FLOOD) {
+ if (flags.val & BR_BCAST_FLOOD)
+ block &= ~BIT(MXL862XX_BRIDGE_PORT_EGRESS_METER_BROADCAST);
+ else
+ block |= BIT(MXL862XX_BRIDGE_PORT_EGRESS_METER_BROADCAST);
+ }
+
+ if (flags.mask & BR_LEARNING)
+ priv->ports[port].learning = !!(flags.val & BR_LEARNING);
+
+ if (block != old_block || (flags.mask & BR_LEARNING)) {
+ priv->ports[port].flood_block = block;
+ ret = mxl862xx_set_bridge_port(ds, port);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
static const struct dsa_switch_ops mxl862xx_switch_ops = {
.get_tag_protocol = mxl862xx_get_tag_protocol,
.setup = mxl862xx_setup,
.port_setup = mxl862xx_port_setup,
+ .port_teardown = mxl862xx_port_teardown,
.phylink_get_caps = mxl862xx_phylink_get_caps,
.port_enable = mxl862xx_port_enable,
.port_disable = mxl862xx_port_disable,
.port_fast_age = mxl862xx_port_fast_age,
+ .set_ageing_time = mxl862xx_set_ageing_time,
+ .port_bridge_join = mxl862xx_port_bridge_join,
+ .port_bridge_leave = mxl862xx_port_bridge_leave,
+ .port_pre_bridge_flags = mxl862xx_port_pre_bridge_flags,
+ .port_bridge_flags = mxl862xx_port_bridge_flags,
+ .port_stp_state_set = mxl862xx_port_stp_state_set,
+ .port_set_host_flood = mxl862xx_port_set_host_flood,
+ .port_fdb_add = mxl862xx_port_fdb_add,
+ .port_fdb_del = mxl862xx_port_fdb_del,
+ .port_fdb_dump = mxl862xx_port_fdb_dump,
+ .port_mdb_add = mxl862xx_port_mdb_add,
+ .port_mdb_del = mxl862xx_port_mdb_del,
};
static void mxl862xx_phylink_mac_config(struct phylink_config *config,
@@ -407,7 +1013,7 @@ static int mxl862xx_probe(struct mdio_device *mdiodev)
struct device *dev = &mdiodev->dev;
struct mxl862xx_priv *priv;
struct dsa_switch *ds;
- int err;
+ int err, i;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
@@ -425,14 +1031,25 @@ static int mxl862xx_probe(struct mdio_device *mdiodev)
ds->ops = &mxl862xx_switch_ops;
ds->phylink_mac_ops = &mxl862xx_phylink_mac_ops;
ds->num_ports = MXL862XX_MAX_PORTS;
+ ds->fdb_isolation = true;
+ ds->max_num_bridges = MXL862XX_MAX_BRIDGES;
+
mxl862xx_host_init(priv);
+ for (i = 0; i < MXL862XX_MAX_PORTS; i++) {
+ priv->ports[i].priv = priv;
+ INIT_WORK(&priv->ports[i].host_flood_work,
+ mxl862xx_host_flood_work_fn);
+ }
+
dev_set_drvdata(dev, ds);
err = dsa_register_switch(ds);
- if (err)
+ if (err) {
mxl862xx_host_shutdown(priv);
-
+ for (i = 0; i < MXL862XX_MAX_PORTS; i++)
+ cancel_work_sync(&priv->ports[i].host_flood_work);
+ }
return err;
}
@@ -440,6 +1057,7 @@ static void mxl862xx_remove(struct mdio_device *mdiodev)
{
struct dsa_switch *ds = dev_get_drvdata(&mdiodev->dev);
struct mxl862xx_priv *priv;
+ int i;
if (!ds)
return;
@@ -449,12 +1067,21 @@ static void mxl862xx_remove(struct mdio_device *mdiodev)
dsa_unregister_switch(ds);
mxl862xx_host_shutdown(priv);
+
+ /* Cancel any pending host flood work. dsa_unregister_switch()
+ * has already called port_teardown (which sets setup_done=false),
+ * but a worker could still be blocked on rtnl_lock(). Since we
+ * are now outside RTNL, cancel_work_sync() will not deadlock.
+ */
+ for (i = 0; i < MXL862XX_MAX_PORTS; i++)
+ cancel_work_sync(&priv->ports[i].host_flood_work);
}
static void mxl862xx_shutdown(struct mdio_device *mdiodev)
{
struct dsa_switch *ds = dev_get_drvdata(&mdiodev->dev);
struct mxl862xx_priv *priv;
+ int i;
if (!ds)
return;
@@ -465,6 +1092,9 @@ static void mxl862xx_shutdown(struct mdio_device *mdiodev)
mxl862xx_host_shutdown(priv);
+ for (i = 0; i < MXL862XX_MAX_PORTS; i++)
+ cancel_work_sync(&priv->ports[i].host_flood_work);
+
dev_set_drvdata(&mdiodev->dev, NULL);
}
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.h b/drivers/net/dsa/mxl862xx/mxl862xx.h
index 3ca0d386f3e8f..8d03dd24faeeb 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
@@ -4,15 +4,114 @@
#define __MXL862XX_H
#include <linux/mdio.h>
+#include <linux/workqueue.h>
#include <net/dsa.h>
+struct mxl862xx_priv;
+
#define MXL862XX_MAX_PORTS 17
+#define MXL862XX_DEFAULT_BRIDGE 0
+#define MXL862XX_MAX_BRIDGES 48
+#define MXL862XX_MAX_BRIDGE_PORTS 128
+
+/* Number of __le16 words in a firmware portmap (128-bit bitmap). */
+#define MXL862XX_FW_PORTMAP_WORDS (MXL862XX_MAX_BRIDGE_PORTS / 16)
+
+/**
+ * mxl862xx_fw_portmap_set_bit - set a single port bit in a firmware portmap
+ * @map: firmware portmap array (MXL862XX_FW_PORTMAP_WORDS entries)
+ * @port: port index (0..MXL862XX_MAX_BRIDGE_PORTS-1)
+ */
+static inline void mxl862xx_fw_portmap_set_bit(__le16 *map, int port)
+{
+ map[port / 16] |= cpu_to_le16(BIT(port % 16));
+}
+
+/**
+ * mxl862xx_fw_portmap_clear_bit - clear a single port bit in a firmware portmap
+ * @map: firmware portmap array (MXL862XX_FW_PORTMAP_WORDS entries)
+ * @port: port index (0..MXL862XX_MAX_BRIDGE_PORTS-1)
+ */
+static inline void mxl862xx_fw_portmap_clear_bit(__le16 *map, int port)
+{
+ map[port / 16] &= ~cpu_to_le16(BIT(port % 16));
+}
+
+/**
+ * mxl862xx_fw_portmap_is_empty - check whether a firmware portmap has no
+ * bits set
+ * @map: firmware portmap array (MXL862XX_FW_PORTMAP_WORDS entries)
+ *
+ * Return: true if every word in @map is zero.
+ */
+static inline bool mxl862xx_fw_portmap_is_empty(const __le16 *map)
+{
+ int i;
+
+ for (i = 0; i < MXL862XX_FW_PORTMAP_WORDS; i++)
+ if (map[i])
+ return false;
+ return true;
+}
+
+/**
+ * struct mxl862xx_port - per-port state tracked by the driver
+ * @priv: back-pointer to switch private data; needed by
+ * deferred work handlers to access ds and priv
+ * @fid: firmware FID for the permanent single-port bridge;
+ * kept alive for the lifetime of the port so traffic is
+ * never forwarded while the port is unbridged
+ * @flood_block: bitmask of firmware meter indices that are currently
+ * rate-limiting flood traffic on this port (zero-rate
+ * meters used to block flooding)
+ * @learning: true when address learning is enabled on this port
+ * @setup_done: set at end of port_setup, cleared at start of
+ * port_teardown; guards deferred work against
+ * acting on torn-down state
+ * @host_flood_uc: desired host unicast flood state (true = flood);
+ * updated atomically by port_set_host_flood, consumed
+ * by the deferred host_flood_work
+ * @host_flood_mc: desired host multicast flood state (true = flood)
+ * @host_flood_work: deferred work for applying host flood changes;
+ * port_set_host_flood runs in atomic context (under
+ * netif_addr_lock) so firmware calls must be deferred.
+ * The worker acquires rtnl_lock() to serialize with
+ * DSA callbacks and checks @setup_done to avoid
+ * acting on torn-down ports.
+ */
+struct mxl862xx_port {
+ struct mxl862xx_priv *priv;
+ u16 fid;
+ unsigned long flood_block;
+ bool learning;
+ bool setup_done;
+ bool host_flood_uc;
+ bool host_flood_mc;
+ struct work_struct host_flood_work;
+};
+/**
+ * struct mxl862xx_priv - driver private data for an MxL862xx switch
+ * @ds: pointer to the DSA switch instance
+ * @mdiodev: MDIO device used to communicate with the switch firmware
+ * @crc_err_work: deferred work for shutting down all ports on MDIO CRC errors
+ * @crc_err: set atomically before CRC-triggered shutdown, cleared after
+ * @drop_meter: index of the single shared zero-rate firmware meter used
+ * to unconditionally drop traffic (used to block flooding)
+ * @ports: per-port state, indexed by switch port number
+ * @bridges: maps DSA bridge number to firmware bridge ID;
+ * zero means no firmware bridge allocated for that
+ * DSA bridge number. Indexed by dsa_bridge.num
+ * (0 .. ds->max_num_bridges).
+ */
struct mxl862xx_priv {
struct dsa_switch *ds;
struct mdio_device *mdiodev;
struct work_struct crc_err_work;
unsigned long crc_err;
+ u16 drop_meter;
+ struct mxl862xx_port ports[MXL862XX_MAX_PORTS];
+ u16 bridges[MXL862XX_MAX_BRIDGES + 1];
};
#endif /* __MXL862XX_H */
--
2.53.0
^ permalink raw reply related
* Re: [PATCH io_uring-7.1 v3 0/6] follow up zcrx fixes
From: Jens Axboe @ 2026-04-01 13:39 UTC (permalink / raw)
To: io-uring, Pavel Begunkov; +Cc: netdev
In-Reply-To: <cover.1774780198.git.asml.silence@gmail.com>
On Tue, 31 Mar 2026 22:07:37 +0100, Pavel Begunkov wrote:
> Follow up fixes for the recent update flagged by review.
>
> v3: include the mmap_offset cast patch from Anas
> v2: reject REG_NODEV + ->rx_buf_size
>
> Anas Iqbal (1):
> io_uring: cast id to u64 before shifting in io_allocate_rbuf_ring()
>
> [...]
Applied, thanks!
[1/6] io_uring/zcrx: reject REG_NODEV with large rx_buf_size
commit: f13bf23a829f39f21ac6e0757efe11304fff0e6a
[2/6] io_uring: cast id to u64 before shifting in io_allocate_rbuf_ring()
commit: 42cf59688914875d6eb06c9674a4b33984e6dd45
[3/6] io_uring/zcrx: don't use mark0 for allocating xarray
commit: f168ed94b91a63165b99abb868eb3df01b5b5aef
[4/6] io_uring/zcrx: don't clear not allocated niovs
commit: 600bf4721005b73763089328f17e5248dba2c034
[5/6] io_uring/zcrx: use dma_len for chunk size calculation
commit: 5ad086c24beda5154ca21495cbc32903d6d64337
[6/6] io_uring/zcrx: use correct mmap off constants
commit: a49d0b79efc770727ccc9f962afa22fdb99736de
Best regards,
--
Jens Axboe
^ permalink raw reply
* Re: [SECURITY] ipv6: opt_flen wrap via repeated IPV6_DSTOPTS cmsgs triggers skb_under_panic (local DoS)
From: Greg KH @ 2026-04-01 13:39 UTC (permalink / raw)
To: 钱一铭; +Cc: security, netdev
In-Reply-To: <CAL_bE8+MpRX-B4wn_-9kqMDQXJDxV2GgTJi_X+WA9GoaoV_gMA@mail.gmail.com>
On Wed, Apr 01, 2026 at 08:56:22PM +0800, 钱一铭 wrote:
> Hi,
>
> I believe I found a locally triggerable kernel bug in the IPv6 sendmsg
> ancillary-data path that can panic the kernel via `skb_under_panic()`
> (local DoS).
As you've sent this to netdev, which is public, no need for security@k.o
to get involved here.
Also, as you have a reproducer, can you make a patch to fix this so you
get full credit for the fix?
thanks,
greg k-h
^ permalink raw reply
* Re: [PATCH net-next v4 1/2] net: hsr: require valid EOT supervision TLV
From: Fernando Fernandez Mancera @ 2026-04-01 13:44 UTC (permalink / raw)
To: Luka Gejak, davem, edumazet, kuba, pabeni; +Cc: netdev, fmaurer, horms
In-Reply-To: <7540A920-76E7-4768-9E62-6666D0FECFA9@linux.dev>
On 4/1/26 3:31 PM, Luka Gejak wrote:
> On April 1, 2026 2:05:49 PM GMT+02:00, Fernando Fernandez Mancera <fmancera@suse.de> wrote:
>> On 4/1/26 1:06 PM, Luka Gejak wrote:
>>> On April 1, 2026 11:52:02 AM GMT+02:00, Fernando Fernandez Mancera <fmancera@suse.de> wrote:
>>>> On 4/1/26 11:23 AM, luka.gejak@linux.dev wrote:
>>>>> From: Luka Gejak <luka.gejak@linux.dev>
>>>>>
>>>>> Supervision frames are only valid if terminated with a zero-length EOT
>>>>> TLV. The current check fails to reject non-EOT entries as the terminal
>>>>> TLV, potentially allowing malformed supervision traffic.
>>>>>
>>>>> Fix this by strictly requiring the terminal TLV to be HSR_TLV_EOT
>>>>> with a length of zero.
>>>>>
>>>>> Reviewed-by: Felix Maurer <fmaurer@redhat.com>
>>>>> Signed-off-by: Luka Gejak <luka.gejak@linux.dev>
>>>>> ---
>>>>
>>>> Hi,
>>>>
>>>> This has not been reviewed by Felix. Felix provided his Reviewed-by tag for the v1 which was completely different than this.
>>>>
>>>> Revisions of this patch:
>>>>
>>>> v3: https://lore.kernel.org/all/20260329112313.17164-4-luka.gejak@linux.dev/
>>>>
>>>> v2: https://lore.kernel.org/all/20260326154715.38405-4-luka.gejak@linux.dev/
>>>>
>>>> v1: https://lore.kernel.org/all/20260324143503.187642-4-luka.gejak@linux.dev/
>>>>
>>>> Are these contributions LLM/AI generated? I believe so based on the email history.
>>>>
>>>> AI generated review on rtl8723bs: https://lore.kernel.org/all/B2394A3C-25FD-4CEA-8557-3E68F1F60357@linux.dev/
>>>>
>>>> Another AI generated review on rtl8723bs: https://lore.kernel.org/all/3831D599-655E-40B2-9E5D-9DF956013088@linux.dev/
>>>>
>>>> Likely an AI generated review on a 1 year old HSR patch: https://lore.kernel.org/all/DHFG26KI6L23.1YCOVQ5SSYMO5@linux.dev/
>>>>
>>>> If these are indeed, AI generated contributions or reviews they should be disclosed beforehand. Also there is the Assisted-by: tag. Also note that developer must take full responsibility for the contribution which means understanding it completely.
>>>>
>>>> https://docs.kernel.org/process/coding-assistants.html#signed-off-by-and-developer-certificate-of-origin
>>>>
>>>> Thanks,
>>>> Fernando.
>>>
>>> Hi Fernando,
>>> About the Reviewed-by tag, that was my mistake. I forgot to remove it
>>> when rebasing. And about AI. I’ve been using it to help format my
>>> emails and translate them into English since it isn't my native
>>> language. However, the technical logic and the code itself are my own
>>> work, written without AI.
>>
>> So I don't think this change in code is related to rebasing at all. The code in this function is identical when comparing net and net-next tree so why a rebase would cause such change in the code? Am I missing something here?
>>
>> In addition to that, the new logic does not make any sense. What motivated the diff from v3 to v4? All that information is missing.
>>
>> Thanks,
>> Fernando.
>>
>> Should I send v5, and if so, what should I
>>> do besides stripping the Reviewed-by tag? I've read the documentation
>>> on coding assistants you linked and will make sure to follow it on the
>>> next revision.
>>> Best regards,
>>> Luka Gejak
>>>
>>
>
> Hi Fernando,
> These 2 patches were in the same patch series as 2 other patches.
> Then, when rebasing to edit 3rd patch in original patch series(3/4), I
> forgot to remove the Reviewed-by tag(I ran git commit --amend
> --no-edit). Then I separated 1st 2 and 2nd 2 patches into separate
> series (one for net and other for net-next) because I was instructed
> to do so by Jakub Kicinski. His email:
>> I think that patches 1 and 2 need to go to net with a Fixes tag.
>> They look like run of the mill bug fixes. 3 and 4 are logical
>> fixes and change behavior so net-next makes sense.
>>
This is not addressing my concern. I am not complaining that it went
into a separate series. That makes sense. The v3 version of the patch
"net: hsr: require valid EOT supervision TLV" is now something
completely different but the commit message is the same.
v3: https://lore.kernel.org/all/20260329112313.17164-4-luka.gejak@linux.dev/
I could guess the reason is that in order to make sure the last TLV is a
EOT TLV with 0 length but all that explanation is missing on the commit
message.
>> FWIW AI has something to say about patch 3, I did not investigate:
>> https://sashiko.dev/#/patchset/20260329112313.17164-2-luka.gejak@linux.dev
>> --
>> pw-bot: cr
^ permalink raw reply
* [PATCH net-next v9 0/4] net: dsa: mxl862xx: add support for bridge offloading
From: Daniel Golle @ 2026-04-01 13:34 UTC (permalink / raw)
To: Daniel Golle, Andrew Lunn, Vladimir Oltean, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, David Yang,
Simon Horman, Russell King, netdev, linux-kernel
Cc: Frank Wunderlich, Chad Monroe, Cezary Wilmanski, Liang Xu,
Benny (Ying-Tsan) Weng, Jose Maria Verdu Munoz, Avinash Jayaraman,
John Crispin
As a next step to complete the mxl862xx DSA driver, add support for
offloading forwarding between bridged ports to the switch hardware.
This works pretty much without any big surprises, apart from two
subtleties:
* per-port control over flooding behavior has to be implemented by
(ab)using a 0-rate QoS meters as stopper in lack of any better
option.
* STP state transition unconditionally enables learning on a port
even if it was previously explicitely disabled (a firmware bug)
Note that as the driver is still lacking all VLAN features (which
are going to be added next), at this point some of the
bridge_vlan_aware.sh tests are failing after applying this series.
This is expected and cannot be avoided without implementing
port_vlan_filtering + port_vlan_add/del. And adding both bridge and
VLAN offloading at the same time would be too much for anyone to
review, so VLAN support is going to be submitted in a follow-up
series immediately after this series has been accepted.
All other relevant selftests (including bridge_vlan_unaware.sh) are
still passing.
Inspired by the comments received from Paolo Abeni as reply to v5
the driver now no longer caches bridge port membership in the
driver, but instead imports an existing helper from yt921x.c to dsa.h
in order to allow the driver to easily iterate over bridge members.
The mapping between DSA bridge num and firmware bridge ID is done
using a simple fixed-size array in mxl862xx_priv.
Changes since v8:
* eliminate all driver-cached portmaps as way to address concerns
raised by AI review regarding stale state after error on
.port_bridge_join which would prevent later freeing the firmware
refcounted bridge port. Rollback on error is not perfect in this
way (but it isn't in any other driver either), but at least the
DSA-assumed state is sync'ed to hardware at any later change
(such as other ports leaving the bridge)
* always apply host-flood setting to single-port bridge (do not
loose state)
* cancel any pending host_flood_work in probe error path
* fix missing p->learning reset when reverting port to standalone
on bridge leave
* remove mxl862xx_add_single_port_bridge(); fold bridge FID
allocation and mxl862xx_bridge_config_fwd() call into port_setup()
directly, and move standalone port state reset (portmap, learning,
flood_block) into mxl862xx_set_bridge_port() for the !dp->bridge
user-port path
* refactor mxl862xx_allocate_bridge() to return the allocated FID
directly, with a negative value indicating error
* document in commit message that MASK_VLAN_BASED_MAC_LEARNING is
intentionally absent as SVL is the firmware's reset default
* add comment noting that a FID leaked on port_setup() failure is
tolerable as the port transitions to unused and the pool accounts
for this
Changes since v7:
* add missing fields to kernel-doc for struct mxl862xx_priv
* const'ify some function parameters
Changes since v6:
* drop wrong addition of 'priv' pointer to struct dsa_bridge
-> use simple array to track firmware bridge to dsa bridge mapping
* move existing dsa_bridge_ports() helper from yt921x.c to dsa.h
* replace propesed dsa_bridge_for_each_member() by new
dsa_switch_for_each_bridge_member() taking (struct net_device *)
instead of (struct dsa_bridge *), aligned with the existing
helpers
* fix bridge allocation resource leak in case of
mxl862xx_sync_bridge_members() failing for the first port to join a
bridge
* zero-initialize struct mxl862xx_cfg in mxl862xx_set_ageing_time
* set initial=1 to always reset cursor at start when interating over
FDB in .fdb_dump
* hack zero-rate meter to truly always block
* use asynchronous worker for port_set_host_flood which runs from
atomic context and hence cannot sleep
* reorder function to minimize diffstat of planned follow-up
commits
Changes since v5:
* add new DSA helpers to interate over bridge members
* add driver-private pointer to struct dsa_bridge
* eliminate struct mxl862xx_bridge and driver-private bridge list,
store firmware bridge FID in new dsa_bridge->priv instead
* rework sync_bridge_members() to use dsa_bridge_for_each_member()
instead of for_each_set_bit() on driver-private portmap
* rework port_bridge_join/leave to use dsa_bridge.priv for first-
member detection and dsa_bridge_ports() for empty-bridge check
* derive active FID from dp->bridge->priv in set_bridge_port()
* simplify allocate_bridge()/free_bridge() to take struct dsa_bridge
pointer directly, drop kzalloc/kfree/list management
* simplify get_fid() to read db.bridge.priv directly
* remove mxl862xx_find_bridge()
* remove unnecessary default bridge config call in setup()
Changes since v4:
* move link-local check before dsa_strip_etype_header()
* introduce port_map helpers
* properly implement port_mdb_{add,del} operations
Changes since v3:
* add missing cpu_to_le32 in mxl862xx_bridge_config_fwd()
* use little-endian 32-bit type for (unused) age_timer API field
* better comment in port_set_host_flood() documenting architectural
limitation
* fix typo in comment "matche" should be "matches"
* few whitespace fixes
Changes since v2:
* refactor .port_bridge_join and .port_bridge_leave as requested
by Vladimir Oltean
* include linux/etherdevice.h which was missing and causing build
to fail (it accidentally slipped into a follow-up patch)
* remove left-over manual reset of learning state for port leaving
bridge
* remove unnecessary call to mxl862xx_port_fast_age() for port
leaving bridge
* add kernel-doc comments in mxl862xx.h instead of sporadic inline
comments covering only some of the struct members
* some other minor cosmetics (linebreaks, whitespace) here and there
Changes since v1:
* don't set dsa_default_offload_fwd_mark() on link-local frames
* fix kernel-doc comments in API header
* use bitfield helpers for compound tci field in fdb API
* add missing endian conversion for mxl862xx_stp_port_cfg.port_state
as well as mxl862xx_mac_table_read.tci (spotted by AI review)
* drop manually resetting port learning state on bridge<->standalone
transitions, DSA framework takes care of that
* don't abort updating bridge ports on error, return error at the end
* report error in mxl862xx_port_bridge_leave()
* create mxl862xx_get_fid() helper and use it in
mxl862xx_port_fdb_add() and mxl862xx_port_fdb_del()
* propagete error of callback function in mxl862xx_port_fdb_dump()
* manually mxl862xx_port_fast_age() in mxl862xx_port_stp_state_set()
to avoid FDB poisoning due to race condition
Daniel Golle (4):
net: dsa: move dsa_bridge_ports() helper to dsa.h
net: dsa: add bridge member iteration macro
dsa: tag_mxl862xx: set dsa_default_offload_fwd_mark()
net: dsa: mxl862xx: implement bridge offloading
drivers/net/dsa/mxl862xx/mxl862xx-api.h | 225 +++++++-
drivers/net/dsa/mxl862xx/mxl862xx-cmd.h | 20 +-
drivers/net/dsa/mxl862xx/mxl862xx.c | 736 ++++++++++++++++++++++--
drivers/net/dsa/mxl862xx/mxl862xx.h | 99 ++++
drivers/net/dsa/yt921x.c | 13 -
include/net/dsa.h | 16 +
net/dsa/tag_mxl862xx.c | 3 +
7 files changed, 1041 insertions(+), 71 deletions(-)
--
2.53.0
^ permalink raw reply
* [PATCH net-next v9 1/4] net: dsa: move dsa_bridge_ports() helper to dsa.h
From: Daniel Golle @ 2026-04-01 13:34 UTC (permalink / raw)
To: Daniel Golle, Andrew Lunn, Vladimir Oltean, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, David Yang,
Simon Horman, Russell King, netdev, linux-kernel
Cc: Frank Wunderlich, Chad Monroe, Cezary Wilmanski, Liang Xu,
Benny (Ying-Tsan) Weng, Jose Maria Verdu Munoz, Avinash Jayaraman,
John Crispin
In-Reply-To: <cover.1775049897.git.daniel@makrotopia.org>
The yt921x driver contains a helper to create a bitmap of ports
which are members of a bridge.
Move the helper as static inline function into dsa.h, so other driver
can make use of it as well.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
v9: no changes
v8: no changes
v7: new patch
---
drivers/net/dsa/yt921x.c | 13 -------------
include/net/dsa.h | 13 +++++++++++++
2 files changed, 13 insertions(+), 13 deletions(-)
diff --git a/drivers/net/dsa/yt921x.c b/drivers/net/dsa/yt921x.c
index 904613f4694ad..5b66109ecc235 100644
--- a/drivers/net/dsa/yt921x.c
+++ b/drivers/net/dsa/yt921x.c
@@ -2154,19 +2154,6 @@ yt921x_bridge_join(struct yt921x_priv *priv, int port, u16 ports_mask)
return 0;
}
-static u32
-dsa_bridge_ports(struct dsa_switch *ds, const struct net_device *bdev)
-{
- struct dsa_port *dp;
- u32 mask = 0;
-
- dsa_switch_for_each_user_port(dp, ds)
- if (dsa_port_offloads_bridge_dev(dp, bdev))
- mask |= BIT(dp->index);
-
- return mask;
-}
-
static int
yt921x_bridge_flags(struct yt921x_priv *priv, int port,
struct switchdev_brport_flags flags)
diff --git a/include/net/dsa.h b/include/net/dsa.h
index 6c17446f3dcc2..e93b4feaca966 100644
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -831,6 +831,19 @@ dsa_tree_offloads_bridge_dev(struct dsa_switch_tree *dst,
return false;
}
+static inline u32
+dsa_bridge_ports(struct dsa_switch *ds, const struct net_device *bdev)
+{
+ struct dsa_port *dp;
+ u32 mask = 0;
+
+ dsa_switch_for_each_user_port(dp, ds)
+ if (dsa_port_offloads_bridge_dev(dp, bdev))
+ mask |= BIT(dp->index);
+
+ return mask;
+}
+
static inline bool dsa_port_tree_same(const struct dsa_port *a,
const struct dsa_port *b)
{
--
2.53.0
^ permalink raw reply related
* Re: [BUG][SECURITY?] ipv6: opt_flen wrap via repeated IPV6_DSTOPTS cmsgs triggers skb_under_panic (local DoS)
From: Eric Dumazet @ 2026-04-01 13:46 UTC (permalink / raw)
To: 钱一铭; +Cc: security, netdev
In-Reply-To: <CAL_bE8JNzawgr5OX5m+3jnQDHry2XxhQT5=jThW1zDPtUikRYA@mail.gmail.com>
On Wed, Apr 1, 2026 at 6:19 AM 钱一铭 <yimingqian591@gmail.com> wrote:
>
> Hi,
>
> I believe I found a locally triggerable kernel bug in the IPv6 sendmsg
> ancillary-data path that can panic the kernel via `skb_under_panic()`
> (local DoS).
OK, but why sending this report twice ?
Do you have a patch already, or are you asking one of us to provide it ?
Please do not CC security@ to anything you made public on netdev@,
this is not welcomed.
^ permalink raw reply
* [syzbot] [net?] KASAN: global-out-of-bounds Read in ip6gre_header
From: syzbot @ 2026-04-01 13:50 UTC (permalink / raw)
To: davem, dsahern, edumazet, horms, kuba, linux-kernel, netdev,
pabeni, syzkaller-bugs
Hello,
syzbot found the following issue on:
HEAD commit: cf0d9080c6f7 Merge branch 'net-hsr-subsystem-cleanups-and-..
git tree: net-next
console output: https://syzkaller.appspot.com/x/log.txt?x=1760a360580000
kernel config: https://syzkaller.appspot.com/x/.config?x=71d49d824b43a0d9
dashboard link: https://syzkaller.appspot.com/bug?extid=d8c285748fa7292580a9
compiler: Debian clang version 21.1.8 (++20251221033036+2078da43e25a-1~exp1~20251221153213.50), Debian LLD 21.1.8
syz repro: https://syzkaller.appspot.com/x/repro.syz?x=11e438d2580000
C reproducer: https://syzkaller.appspot.com/x/repro.c?x=117f19ca580000
Downloadable assets:
disk image: https://storage.googleapis.com/syzbot-assets/4271eafdaa2c/disk-cf0d9080.raw.xz
vmlinux: https://storage.googleapis.com/syzbot-assets/984de2c215cb/vmlinux-cf0d9080.xz
kernel image: https://storage.googleapis.com/syzbot-assets/e03ead684811/bzImage-cf0d9080.xz
IMPORTANT: if you fix the issue, please add the following tag to the commit:
Reported-by: syzbot+d8c285748fa7292580a9@syzkaller.appspotmail.com
==================================================================
BUG: KASAN: global-out-of-bounds in ip6gre_header+0x5bf/0x860 net/ipv6/ip6_gre.c:1400
Read of size 16 at addr ffffffff8c6c4380 by task dhcpcd/5489
CPU: 0 UID: 0 PID: 5489 Comm: dhcpcd Not tainted syzkaller #0 PREEMPT(full)
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 02/12/2026
Call Trace:
<TASK>
dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
print_address_description mm/kasan/report.c:378 [inline]
print_report+0xba/0x230 mm/kasan/report.c:482
kasan_report+0x117/0x150 mm/kasan/report.c:595
check_region_inline mm/kasan/generic.c:-1 [inline]
kasan_check_range+0x264/0x2c0 mm/kasan/generic.c:200
__asan_memcpy+0x29/0x70 mm/kasan/shadow.c:105
ip6gre_header+0x5bf/0x860 net/ipv6/ip6_gre.c:1400
bond_header_create+0x1ce/0x300 drivers/net/bonding/bond_main.c:1524
dev_hard_header include/linux/netdevice.h:3449 [inline]
lapbeth_data_transmit+0x218/0x2e0 drivers/net/wan/lapbether.c:258
lapb_data_transmit+0x90/0xb0 net/lapb/lapb_iface.c:447
lapb_transmit_buffer+0x163/0x200 net/lapb/lapb_out.c:149
lapb_establish_data_link+0x89/0xe0 net/lapb/lapb_out.c:-1
lapb_device_event+0x4e1/0x670 net/lapb/lapb_iface.c:-1
notifier_call_chain+0x1be/0x400 kernel/notifier.c:85
call_netdevice_notifiers_extack net/core/dev.c:2285 [inline]
call_netdevice_notifiers net/core/dev.c:2299 [inline]
__dev_notify_flags+0x1a9/0x310 net/core/dev.c:9830
netif_change_flags+0xe8/0x1a0 net/core/dev.c:9859
dev_change_flags+0x130/0x260 net/core/dev_api.c:68
devinet_ioctl+0x9f2/0x1b30 net/ipv4/devinet.c:1199
inet_ioctl+0x42a/0x560 net/ipv4/af_inet.c:1011
sock_do_ioctl+0x101/0x320 net/socket.c:1247
sock_ioctl+0x5c6/0x7f0 net/socket.c:1368
vfs_ioctl fs/ioctl.c:51 [inline]
__do_sys_ioctl fs/ioctl.c:597 [inline]
__se_sys_ioctl+0xfc/0x170 fs/ioctl.c:583
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x14d/0xf80 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7fb458496378
Code: 00 00 48 8d 44 24 08 48 89 54 24 e0 48 89 44 24 c0 48 8d 44 24 d0 48 89 44 24 c8 b8 10 00 00 00 c7 44 24 b8 10 00 00 00 0f 05 <89> c2 3d 00 f0 ff ff 77 07 89 d0 c3 0f 1f 40 00 48 8b 15 49 3a 0d
RSP: 002b:00007ffdede01158 EFLAGS: 00000246 ORIG_RAX: 0000000000000010
RAX: ffffffffffffffda RBX: 0000000000000012 RCX: 00007fb458496378
RDX: 00007ffdede11350 RSI: 0000000000008914 RDI: 0000000000000012
RBP: 0000000000000000 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 00007ffdede214f0
R13: 00007fb458396708 R14: 0000000000000028 R15: 0000000000008914
</TASK>
The buggy address belongs to the variable:
bcast_addr+0x0/0x20
The buggy address belongs to the physical page:
page: refcount:1 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0xc6c4
flags: 0xfff00000002000(reserved|node=0|zone=1|lastcpupid=0x7ff)
raw: 00fff00000002000 ffffea000031b108 ffffea000031b108 0000000000000000
raw: 0000000000000000 0000000000000000 00000001ffffffff 0000000000000000
page dumped because: kasan: bad access detected
page_owner info is not present (never set?)
Memory state around the buggy address:
ffffffff8c6c4280: 00 00 f9 f9 f9 f9 f9 f9 00 00 00 00 06 f9 f9 f9
ffffffff8c6c4300: f9 f9 f9 f9 00 00 00 00 00 02 f9 f9 f9 f9 f9 f9
>ffffffff8c6c4380: 06 f9 f9 f9 00 00 00 00 00 06 f9 f9 f9 f9 f9 f9
^
ffffffff8c6c4400: 00 00 00 00 00 f9 f9 f9 f9 f9 f9 f9 00 00 00 00
ffffffff8c6c4480: 00 05 f9 f9 f9 f9 f9 f9 00 00 00 00 00 06 f9 f9
==================================================================
---
This report is generated by a bot. It may contain errors.
See https://goo.gl/tpsmEJ for more information about syzbot.
syzbot engineers can be reached at syzkaller@googlegroups.com.
syzbot will keep track of this issue. See:
https://goo.gl/tpsmEJ#status for how to communicate with syzbot.
If the report is already addressed, let syzbot know by replying with:
#syz fix: exact-commit-title
If you want syzbot to run the reproducer, reply with:
#syz test: git://repo/address.git branch-or-commit-hash
If you attach or paste a git patch, syzbot will apply it before testing.
If you want to overwrite report's subsystems, reply with:
#syz set subsystems: new-subsystem
(See the list of subsystem names on the web dashboard)
If the report is a duplicate of another one, reply with:
#syz dup: exact-subject-of-another-report
If you want to undo deduplication, reply with:
#syz undup
^ permalink raw reply
* Re: [BUG][SECURITY?] ipv6: opt_flen wrap via repeated IPV6_DSTOPTS cmsgs triggers skb_under_panic (local DoS)
From: 钱一铭 @ 2026-04-01 13:51 UTC (permalink / raw)
To: Eric Dumazet; +Cc: security, netdev
In-Reply-To: <CANn89i+XNV-Axy3gDSdVadBaHUYqvxPxJ_OGyZYo8=-EdqRqtA@mail.gmail.com>
> OK, but why sending this report twice ?
The email system reported a problem with my email. I assumed it failed
to send, so I resent it. I apologize for the inconvenience.
> Please do not CC security@ to anything you made public on netdev@,
this is not welcomed.
Okay, I'll pay attention to that. I'll standardize my vulnerability
reporting behavior in the future.
On Wed, Apr 1, 2026 at 9:46 PM Eric Dumazet <edumazet@google.com> wrote:
>
> On Wed, Apr 1, 2026 at 6:19 AM 钱一铭 <yimingqian591@gmail.com> wrote:
> >
> > Hi,
> >
> > I believe I found a locally triggerable kernel bug in the IPv6 sendmsg
> > ancillary-data path that can panic the kernel via `skb_under_panic()`
> > (local DoS).
>
> OK, but why sending this report twice ?
>
> Do you have a patch already, or are you asking one of us to provide it ?
>
> Please do not CC security@ to anything you made public on netdev@,
> this is not welcomed.
^ permalink raw reply
* [PATCH net] net: lapbether: handle NETDEV_PRE_TYPE_CHANGE
From: Eric Dumazet @ 2026-04-01 13:53 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, Andrew Lunn, netdev, eric.dumazet, Eric Dumazet,
syzbot+d8c285748fa7292580a9
lapbeth_data_transmit() expects the underlying device type
to be ARPHRD_ETHER.
Returning NOTIFY_BAD from lapbeth_device_event() makes sure
bonding driver can not break this expectation.
Fixes: 872254dd6b1f ("net/bonding: Enable bonding to enslave non ARPHRD_ETHER")
Reported-by: syzbot+d8c285748fa7292580a9@syzkaller.appspotmail.com
Closes: https://lore.kernel.org/netdev/69cd22a1.050a0220.70c3a.0002.GAE@google.com/T/#u
Signed-off-by: Eric Dumazet <edumazet@google.com>
---
drivers/net/wan/lapbether.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/drivers/net/wan/lapbether.c b/drivers/net/wan/lapbether.c
index f357a7ac70ac4756967730fa61c07258b4b3ac00..5c91160dd68cc1dfa4308d38fb111032206fae75 100644
--- a/drivers/net/wan/lapbether.c
+++ b/drivers/net/wan/lapbether.c
@@ -473,6 +473,9 @@ static int lapbeth_device_event(struct notifier_block *this,
if (lapbeth)
lapbeth_free_device(lapbeth);
break;
+ case NETDEV_PRE_TYPE_CHANGE:
+ /* Our underlying device type must not change. */
+ return NOTIFY_BAD;
}
return NOTIFY_DONE;
--
2.53.0.1118.gaef5881109-goog
^ permalink raw reply related
* Re: [BUG][SECURITY?] ipv6: opt_flen wrap via repeated IPV6_DSTOPTS cmsgs triggers skb_under_panic (local DoS)
From: Eric Dumazet @ 2026-04-01 14:10 UTC (permalink / raw)
To: 钱一铭; +Cc: security, netdev
In-Reply-To: <CAL_bE8JNzawgr5OX5m+3jnQDHry2XxhQT5=jThW1zDPtUikRYA@mail.gmail.com>
On Wed, Apr 1, 2026 at 6:19 AM 钱一铭 <yimingqian591@gmail.com> wrote:
>
> Hi,
>
> I believe I found a locally triggerable kernel bug in the IPv6 sendmsg
> ancillary-data path that can panic the kernel via `skb_under_panic()`
> (local DoS).
>
> The core issue is a mismatch between:
>
> - a 16-bit length accumulator (`struct ipv6_txoptions::opt_flen`, type
> `__u16`) and
> - a pointer to the *last* provided destination-options header (`opt->dst1opt`)
>
> when multiple `IPV6_DSTOPTS` control messages (cmsgs) are provided.
>
> ## Affected Area
>
> - `include/net/ipv6.h`:
> - `struct ipv6_txoptions::opt_flen` is `__u16` (wrap possible).
> (lines 291-307, especially 298)
> - `net/ipv6/datagram.c:ip6_datagram_send_ctl()`:
> - Accepts repeated `IPV6_DSTOPTS` and accumulates into `opt_flen`
> without rejecting duplicates. (lines 909-933)
> - `net/ipv6/ip6_output.c:__ip6_append_data()`:
> - Uses `opt->opt_flen + opt->opt_nflen` to compute header
> sizes/headroom decisions. (lines 1448-1466, especially 1463-1465)
> - `net/ipv6/ip6_output.c:__ip6_make_skb()`:
> - Calls `ipv6_push_frag_opts()` if `opt->opt_flen` is non-zero.
> (lines 1930-1934)
> - `net/ipv6/exthdrs.c:ipv6_push_frag_opts()` / `ipv6_push_exthdr()`:
> - Push size comes from `ipv6_optlen(opt->dst1opt)` (based on the
> pointed-to header). (lines 1179-1185 and 1206-1211)
>
> ## Root Cause (Detailed)
>
> 1. `opt_flen` is a 16-bit accumulator:
>
> - `include/net/ipv6.h:298` defines `__u16 opt_flen; /* after fragment hdr */`.
>
> 2. `ip6_datagram_send_ctl()` accepts *repeated* `IPV6_DSTOPTS` cmsgs
> and increments `opt_flen` each time:
>
> - In `net/ipv6/datagram.c:909-933`, for `IPV6_DSTOPTS`:
> - It computes `len = ((hdr->hdrlen + 1) << 3);`
> - It checks `CAP_NET_RAW` using `ns_capable(net->user_ns,
> CAP_NET_RAW)`. (line 922)
> - Then it does:
> - `opt->opt_flen += len;` (line 927)
> - `opt->dst1opt = hdr;` (line 928)
>
> There is no duplicate rejection here (unlike the legacy
> `IPV6_2292DSTOPTS` path which rejects duplicates at
> `net/ipv6/datagram.c:901-904`).
>
> If enough large `IPV6_DSTOPTS` cmsgs are provided, `opt_flen` wraps
> while `dst1opt` still points to a large (2048-byte)
> destination-options header.
>
> In the attached PoC (`poc.c`):
>
> - 32 cmsgs with `hdrlen=255` => `len = (255+1)*8 = 2048`
> - 1 cmsg with `hdrlen=0` => `len = 8`
> - Total increment: `32*2048 + 8 = 65544`, so `(__u16)opt_flen == 8`
> - The last cmsg is 2048 bytes, so `dst1opt` points to a 2048-byte header.
>
> 3. The transmit path sizes headers using the wrapped `opt_flen`:
>
> - In `net/ipv6/ip6_output.c:1463-1465`:
> - `headersize = sizeof(struct ipv6hdr) + (opt ? opt->opt_flen +
> opt->opt_nflen : 0) + ...;`
>
> With wrapped `opt_flen`, `headersize`/headroom decisions underestimate
> what will be pushed later.
>
> 4. When building the final skb, the actual push length comes from
> `dst1opt` and is not limited by wrapped `opt_flen`:
>
> - In `net/ipv6/ip6_output.c:1930-1934`:
> - `if (opt->opt_flen) proto = ipv6_push_frag_opts(skb, opt, proto);`
> - In `net/ipv6/exthdrs.c:1206-1211`, `ipv6_push_frag_opts()` pushes
> `dst1opt` via `ipv6_push_exthdr()`.
> - In `net/ipv6/exthdrs.c:1179-1184`, `ipv6_push_exthdr()` does:
> - `skb_push(skb, ipv6_optlen(opt));`
> - `memcpy(h, opt, ipv6_optlen(opt));`
>
> With insufficient headroom, `skb_push()` underflows and triggers
> `skb_under_panic()` -> `BUG()`:
>
> - `net/core/skbuff.c:2669-2675` (`skb_push()` calls `skb_under_panic()`)
> - `net/core/skbuff.c:207-214` (`skb_panic()` ends in `BUG()`)
>
> ## Trigger Conditions / Privileges
>
> - The `IPV6_DSTOPTS` cmsg path requires `CAP_NET_RAW` in the target
> netns user namespace (`ns_capable(net->user_ns, CAP_NET_RAW)`).
> - Root (or any task with `CAP_NET_RAW`) can trigger this without user
> namespaces.
> - An unprivileged `uid=1000` user can trigger this if unprivileged
> user namespaces are enabled and it can create a userns+netns to obtain
> namespaced `CAP_NET_RAW` (the attached PoC does this).
>
> ## Impact
>
> - Local denial of service: kernel BUG/panic (system crash).
> - Reproducible with a small userspace PoC.
>
> ## Crash Proof (KASAN Kernel Log Excerpt)
>
> This excerpt is from the attached QEMU console log
> (`qemu_upstream_20260401.log`) captured on a KASAN-enabled kernel (see
> `kernel.config`, boot args in `kasan_env.txt`):
>
> ```text
> [+] sending IPv6 UDP sendmsg with 33 IPV6_DSTOPTS cmsgs (len=2048 x32, len=8 x1)
> [*] expect a kernel panic (skb_under_panic) on a vulnerable kernel
> [ 51.243668] skbuff: skb_under_panic: ... put:2048 ...
> [ 51.245216] kernel BUG at net/core/skbuff.c:214!
> [ 51.247204] CPU: 0 UID: 1000 PID: 106 Comm: test ... 7.0.0-rc6+ #1 PREEMPT
> [ 51.252080] ipv6_push_frag_opts+0x84/0x130
> [ 51.252349] __ip6_make_skb+0x1773/0x2300
> [ 51.253728] udpv6_sendmsg+0x1a36/0x2760
> ...
> [ 51.267528] Kernel panic - not syncing: Fatal exception
> ```
>
> ## Affected Kernel / Reproduction
>
> I reproduced this on Linux upstream commit:
>
> - `9147566d801602c9e7fc7f85e989735735bf38ba` (reports as `7.0.0-rc6+`
> in my build)
>
> Reproduction steps (VM recommended; this PoC panics the kernel):
>
> 1. Build a kernel with KASAN enabled using the attached `kernel.config`.
> 2. Boot with the command line in `kasan_env.txt`.
> 3. Compile and run `poc.c` inside the guest:
>
> ```sh
> gcc -O0 -g -static -fno-stack-protector -o poc poc.c
> ./poc
> ```
>
> ## Fix Suggestions
>
> I think at least one of these should be done; doing (1) and (2)
> together is most robust:
>
> 1. Reject duplicate `IPV6_DSTOPTS` cmsgs, similar to how
> `IPV6_2292DSTOPTS` rejects duplicates:
> - If `opt->dst1opt` already set and `cmsg_type == IPV6_DSTOPTS`,
> return `-EINVAL`.
>
> 2. Prevent 16-bit wrap:
> - Use a wider temporary and reject additions that would
> overflow/wrap `opt_flen` (e.g., `if (opt->opt_flen > U16_MAX - len)
> return -EINVAL;` or `check_add_overflow()` with a wide type).
>
> 3. Consider widening `opt_flen` / `opt_nflen` to `u32` and validate
> against sane upper bounds before use.
widening opt_flen would trigger other issues I think because sk_buff
use u16 offsets.
I will test :
diff --git a/net/ipv6/datagram.c b/net/ipv6/datagram.c
index c564b68a056268c7cbc81b5f29f60289ea9e09eb..68b7b97a464b00c490c450c2f31b00570b716365
100644
--- a/net/ipv6/datagram.c
+++ b/net/ipv6/datagram.c
@@ -766,6 +766,7 @@ int ip6_datagram_send_ctl(struct net *net, struct sock *sk,
struct ipv6_rt_hdr *rthdr;
struct ipv6_opt_hdr *hdr;
struct ipv6_txoptions *opt = ipc6->opt;
+ u32 opt_flen = opt->opt_flen;
int len;
int err = 0;
@@ -902,7 +903,7 @@ int ip6_datagram_send_ctl(struct net *net, struct sock *sk,
err = -EINVAL;
goto exit_f;
}
- opt->opt_flen += len;
+ opt_flen += len;
opt->dst1opt = hdr;
break;
@@ -924,7 +925,7 @@ int ip6_datagram_send_ctl(struct net *net, struct sock *sk,
goto exit_f;
}
if (cmsg->cmsg_type == IPV6_DSTOPTS) {
- opt->opt_flen += len;
+ opt_flen += len;
opt->dst1opt = hdr;
} else {
opt->opt_nflen += len;
@@ -978,7 +979,7 @@ int ip6_datagram_send_ctl(struct net *net, struct sock *sk,
opt->opt_nflen += dsthdrlen;
opt->dst0opt = opt->dst1opt;
opt->dst1opt = NULL;
- opt->opt_flen -= dsthdrlen;
+ opt_flen -= dsthdrlen;
}
break;
@@ -1040,6 +1041,9 @@ int ip6_datagram_send_ctl(struct net *net,
struct sock *sk,
goto exit_f;
}
}
+ opt->opt_flen = opt_flen;
+ if (opt->opt_flen != opt_flen)
+ err = -EINVAL;
exit_f:
return err;
^ permalink raw reply
* [PATCH v3 3/4] iommu/generic_pt: disable GCOV for iommu_amdv1.o
From: Konstantin Khorenko @ 2026-04-01 14:20 UTC (permalink / raw)
To: Peter Oberparleiter, Mikhail Zaslonko, Thomas Weißschuh
Cc: Steffen Klassert, Herbert Xu, Masahiro Yamada, Josh Poimboeuf,
Vasileios Almpanis, Pavel Tikhomirov, linux-kernel, netdev,
Konstantin Khorenko, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Arnd Bergmann
In-Reply-To: <20260401142020.1434243-1-khorenko@virtuozzo.com>
With -fprofile-update=atomic in global CFLAGS_GCOV, GCC cannot
constant-fold FIELD_PREP() expressions inside deeply inlined page table
walker functions. GCC creates .constprop clones of the inline level
functions, which get their own GCOV instrumentation that prevents
constant evaluation of compile-time checks.
__no_profile cannot be used here because the entire call chain is
__always_inline functions generated by PT_MAKE_LEVELS() macro, and GCC's
constprop cloning creates new profiled function bodies that bypass the
attribute.
Disable GCOV for iommu_amdv1.o to allow BUILD_BUG_ON / FIELD_PREP
compile-time checks to succeed.
Signed-off-by: Konstantin Khorenko <khorenko@virtuozzo.com>
---
drivers/iommu/generic_pt/fmt/Makefile | 2 ++
1 file changed, 2 insertions(+)
diff --git a/drivers/iommu/generic_pt/fmt/Makefile b/drivers/iommu/generic_pt/fmt/Makefile
index 976b49ec97dc..0305f7fb9e35 100644
--- a/drivers/iommu/generic_pt/fmt/Makefile
+++ b/drivers/iommu/generic_pt/fmt/Makefile
@@ -1,5 +1,7 @@
# SPDX-License-Identifier: GPL-2.0
+GCOV_PROFILE_iommu_amdv1.o := n
+
iommu_pt_fmt-$(CONFIG_IOMMU_PT_AMDV1) += amdv1
iommu_pt_fmt-$(CONFIG_IOMMUFD_TEST) += mock
--
2.43.5
^ permalink raw reply related
* [PATCH v3 1/4] net: fix skb_ext_total_length() BUILD_BUG_ON with CONFIG_GCOV_PROFILE_ALL
From: Konstantin Khorenko @ 2026-04-01 14:20 UTC (permalink / raw)
To: Peter Oberparleiter, Mikhail Zaslonko, Thomas Weißschuh
Cc: Steffen Klassert, Herbert Xu, Masahiro Yamada, Josh Poimboeuf,
Vasileios Almpanis, Pavel Tikhomirov, linux-kernel, netdev,
Konstantin Khorenko, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Arnd Bergmann
In-Reply-To: <20260401142020.1434243-1-khorenko@virtuozzo.com>
When CONFIG_GCOV_PROFILE_ALL=y is enabled, the kernel fails to build:
In file included from <command-line>:
In function 'skb_extensions_init',
inlined from 'skb_init' at net/core/skbuff.c:5214:2:
././include/linux/compiler_types.h:706:45: error: call to
'__compiletime_assert_1490' declared with attribute error:
BUILD_BUG_ON failed: skb_ext_total_length() > 255
CONFIG_GCOV_PROFILE_ALL adds -fprofile-arcs -ftest-coverage
-fno-tree-loop-im to CFLAGS globally. GCC inserts branch profiling
counters into the skb_ext_total_length() loop and, combined with
-fno-tree-loop-im (which disables loop invariant motion), cannot
constant-fold the result.
BUILD_BUG_ON requires a compile-time constant and fails.
The issue manifests in kernels with 5+ SKB extension types enabled
(e.g., after addition of SKB_EXT_CAN, SKB_EXT_PSP). With 4 extensions
GCC can still unroll and fold the loop despite GCOV instrumentation;
with 5+ it gives up.
Mark skb_ext_total_length() with __no_profile to prevent GCOV from
inserting counters into this function. Without counters the loop is
"clean" and GCC can constant-fold it even with -fno-tree-loop-im active.
This allows BUILD_BUG_ON to work correctly while keeping GCOV profiling
for the rest of the kernel.
This also removes the CONFIG_KCOV_INSTRUMENT_ALL preprocessor guard
introduced by d6e5794b06c0, as __no_profile handles both GCOV and KCOV
instrumentation at the root cause level rather than just disabling the
check.
Fixes: 5d21d0a65b57 ("net: generalize calculation of skb extensions length")
Fixes: d6e5794b06c0 ("net: avoid build bug in skb extension length calculation")
Signed-off-by: Konstantin Khorenko <khorenko@virtuozzo.com>
Reviewed-by: Thomas Weißschuh <linux@weissschuh.net>
---
net/core/skbuff.c | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/net/core/skbuff.c b/net/core/skbuff.c
index 0e217041958a..47c7f0ab6e84 100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -5145,7 +5145,7 @@ static const u8 skb_ext_type_len[] = {
#endif
};
-static __always_inline unsigned int skb_ext_total_length(void)
+static __always_inline __no_profile unsigned int skb_ext_total_length(void)
{
unsigned int l = SKB_EXT_CHUNKSIZEOF(struct skb_ext);
int i;
@@ -5159,9 +5159,7 @@ static __always_inline unsigned int skb_ext_total_length(void)
static void skb_extensions_init(void)
{
BUILD_BUG_ON(SKB_EXT_NUM > 8);
-#if !IS_ENABLED(CONFIG_KCOV_INSTRUMENT_ALL)
BUILD_BUG_ON(skb_ext_total_length() > 255);
-#endif
skbuff_ext_cache = kmem_cache_create("skbuff_ext_cache",
SKB_EXT_ALIGN_VALUE * skb_ext_total_length(),
--
2.43.5
^ permalink raw reply related
* [PATCH v3 4/4] gcov: use atomic counter updates to fix concurrent access crashes
From: Konstantin Khorenko @ 2026-04-01 14:20 UTC (permalink / raw)
To: Peter Oberparleiter, Mikhail Zaslonko, Thomas Weißschuh
Cc: Steffen Klassert, Herbert Xu, Masahiro Yamada, Josh Poimboeuf,
Vasileios Almpanis, Pavel Tikhomirov, linux-kernel, netdev,
Konstantin Khorenko, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Arnd Bergmann
In-Reply-To: <20260401142020.1434243-1-khorenko@virtuozzo.com>
GCC's GCOV instrumentation can merge global branch counters with loop
induction variables as an optimization. In inflate_fast(), the inner
copy loops get transformed so that the GCOV counter value is loaded
multiple times to compute the loop base address, start index, and end
bound. Since GCOV counters are global (not per-CPU), concurrent
execution on different CPUs causes the counter to change between loads,
producing inconsistent values and out-of-bounds memory writes.
The crash manifests during IPComp (IP Payload Compression) processing
when inflate_fast() runs concurrently on multiple CPUs:
BUG: unable to handle page fault for address: ffffd0a3c0902ffa
RIP: inflate_fast+1431
Call Trace:
zlib_inflate
__deflate_decompress
crypto_comp_decompress
ipcomp_decompress [xfrm_ipcomp]
ipcomp_input [xfrm_ipcomp]
xfrm_input
At the crash point, the compiler generated three loads from the same
global GCOV counter (__gcov0.inflate_fast+216) to compute base, start,
and end for an indexed loop. Another CPU modified the counter between
loads, making the values inconsistent — the write went 3.4 MB past a
65 KB buffer.
Add -fprofile-update=atomic to CFLAGS_GCOV at the global level in the
top-level Makefile. This tells GCC that GCOV counters may be
concurrently accessed, causing counter updates to use atomic
instructions (lock addq) instead of plain load/store. This prevents
the compiler from merging counters with loop induction variables.
Applying this globally rather than per-subsystem not only addresses the
observed crash in zlib but makes GCOV coverage data more consistent
overall, preventing similar issues in any kernel code path that may
execute concurrently.
Signed-off-by: Konstantin Khorenko <khorenko@virtuozzo.com>
---
Makefile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Makefile b/Makefile
index 6b1d9fb1a6b4..a55ad668d6ba 100644
--- a/Makefile
+++ b/Makefile
@@ -806,7 +806,7 @@ all: vmlinux
CFLAGS_GCOV := -fprofile-arcs -ftest-coverage
ifdef CONFIG_CC_IS_GCC
-CFLAGS_GCOV += -fno-tree-loop-im
+CFLAGS_GCOV += -fno-tree-loop-im -fprofile-update=atomic
endif
export CFLAGS_GCOV
--
2.43.5
^ permalink raw reply related
* [PATCH v3 2/4] net: add __no_profile to skb_extensions_init() for GCOV compatibility
From: Konstantin Khorenko @ 2026-04-01 14:20 UTC (permalink / raw)
To: Peter Oberparleiter, Mikhail Zaslonko, Thomas Weißschuh
Cc: Steffen Klassert, Herbert Xu, Masahiro Yamada, Josh Poimboeuf,
Vasileios Almpanis, Pavel Tikhomirov, linux-kernel, netdev,
Konstantin Khorenko, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Arnd Bergmann
In-Reply-To: <20260401142020.1434243-1-khorenko@virtuozzo.com>
With -fprofile-update=atomic in global CFLAGS_GCOV, GCC still cannot
constant-fold the skb_ext_total_length() loop when it is inlined into a
profiled caller. The existing __no_profile on skb_ext_total_length()
itself is insufficient because after __always_inline expansion the code
resides in the caller's body, which still carries GCOV instrumentation.
Mark skb_extensions_init() with __no_profile so the BUILD_BUG_ON checks
can be evaluated at compile time.
Signed-off-by: Konstantin Khorenko <khorenko@virtuozzo.com>
---
net/core/skbuff.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/net/core/skbuff.c b/net/core/skbuff.c
index 47c7f0ab6e84..99704d6832e2 100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -5156,7 +5156,7 @@ static __always_inline __no_profile unsigned int skb_ext_total_length(void)
return l;
}
-static void skb_extensions_init(void)
+static void __no_profile skb_extensions_init(void)
{
BUILD_BUG_ON(SKB_EXT_NUM > 8);
BUILD_BUG_ON(skb_ext_total_length() > 255);
--
2.43.5
^ permalink raw reply related
* [PATCH v3 0/4] gcov: use -fprofile-update=atomic globally to fix concurrent access crashes on GCOV-enabled kernels
From: Konstantin Khorenko @ 2026-04-01 14:20 UTC (permalink / raw)
To: Peter Oberparleiter, Mikhail Zaslonko, Thomas Weißschuh
Cc: Steffen Klassert, Herbert Xu, Masahiro Yamada, Josh Poimboeuf,
Vasileios Almpanis, Pavel Tikhomirov, linux-kernel, netdev,
Konstantin Khorenko, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Arnd Bergmann
This series adds -fprofile-update=atomic to global CFLAGS_GCOV and
includes preparatory patches that fix build failures exposed by the new
flag.
Background
----------
This work combines and supersedes two previously separate series:
1. Build fix for CONFIG_GCOV_PROFILE_ALL=y - skb_ext_total_length()
BUILD_BUG_ON failure:
https://lore.kernel.org/lkml/20260331165125.959833-1-khorenko@virtuozzo.com/T/#t
2. Runtime crash fix for zlib inflate_fast() - GCOV counter merging
with loop induction variables caused out-of-bounds writes on SMP:
https://lore.kernel.org/lkml/20260330143256.306326-1-khorenko@virtuozzo.com/T/#t
The original zlib fix added -fprofile-update=atomic only to zlib
Makefiles. During review, it was suggested to apply the flag globally
instead, as it not only fixes the zlib crash but makes GCOV coverage
data more consistent overall. The GCC bug report for the underlying
compiler issue is at (and they also said just to use -fprofile-update=atomic):
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=124749
Adding -fprofile-update=atomic to global CFLAGS_GCOV exposed two
additional build failures that are addressed by preparatory patches
in this series.
Series overview
---------------
Patch 1/4: net: fix skb_ext_total_length() BUILD_BUG_ON
Pre-existing build failure with CONFIG_GCOV_PROFILE_ALL=y. GCOV
counters prevent GCC from constant-folding the skb_ext_total_length()
loop. Fixed by adding __no_profile to the function and removing the
now-unnecessary CONFIG_KCOV_INSTRUMENT_ALL preprocessor guard.
(This is v2 of the previously posted standalone patch.)
Patch 2/4: net: add __no_profile to skb_extensions_init()
With -fprofile-update=atomic, __no_profile on skb_ext_total_length()
alone is insufficient - after __always_inline expansion the code
resides in the caller's body which still has GCOV instrumentation.
Mark the caller with __no_profile as well.
Patch 3/4: iommu/generic_pt: disable GCOV for iommu_amdv1.o
FIELD_PREP() compile-time checks fail because the entire call chain
is __always_inline functions generated by PT_MAKE_LEVELS() macro,
and GCC's .constprop cloning creates new profiled function bodies
that bypass __no_profile. Disable GCOV for this file.
Patch 4/4: gcov: use atomic counter updates to fix concurrent crashes
The main fix. Add -fprofile-update=atomic to CFLAGS_GCOV in the
top-level Makefile. This tells GCC to use atomic instructions for
GCOV counter updates, preventing the compiler from merging counters
with loop induction variables. This fixes observed crashes in zlib's
inflate_fast() during concurrent IPComp decompression and makes GCOV
data reliable across the entire kernel on SMP systems.
The crash that motivated this work
-----------------------------------
Observed during LTP IPComp stress testing on a GCOV-enabled kernel:
BUG: unable to handle page fault for address: ffffd0a3c0902ffa
RIP: inflate_fast+1431
Call Trace:
zlib_inflate
__deflate_decompress
crypto_comp_decompress
ipcomp_decompress [xfrm_ipcomp]
ipcomp_input [xfrm_ipcomp]
xfrm_input
GCC merged a global GCOV counter with the loop induction variable.
Another CPU modified the counter between loads, causing a write 3.4 MB
past a 65 KB buffer. -fprofile-update=atomic forces atomic counter
updates and prevents this merging.
Testing
-------
- Build-tested with CONFIG_GCOV_PROFILE_ALL=y using GCC 11.4.1 and
GCC 16.0.1 20260327 (experimental). Both fail without the series,
both succeed with the full series applied.
- Assembly-verified that -fprofile-update=atomic prevents counter-IV
merging in inflate_fast() on both compiler versions.
Konstantin Khorenko (4):
net: fix skb_ext_total_length() BUILD_BUG_ON with CONFIG_GCOV_PROFILE_ALL
net: add __no_profile to skb_extensions_init() for GCOV compatibility
iommu/generic_pt: disable GCOV for iommu_amdv1.o
gcov: use atomic counter updates to fix concurrent access crashes
Makefile | 2 +-
drivers/iommu/generic_pt/fmt/Makefile | 2 ++
net/core/skbuff.c | 5 ++---
3 files changed, 5 insertions(+), 4 deletions(-)
--
2.43.5
^ permalink raw reply
* Re: [BUG][SECURITY?] ipv6: opt_flen wrap via repeated IPV6_DSTOPTS cmsgs triggers skb_under_panic (local DoS)
From: Eric Dumazet @ 2026-04-01 14:24 UTC (permalink / raw)
To: 钱一铭; +Cc: security, netdev
In-Reply-To: <CANn89iK57oiF0T0Z=t7okBuZbCpn+Bt746nxL2+iaQgXPyBb7Q@mail.gmail.com>
On Wed, Apr 1, 2026 at 7:10 AM Eric Dumazet <edumazet@google.com> wrote:
>
> On Wed, Apr 1, 2026 at 6:19 AM 钱一铭 <yimingqian591@gmail.com> wrote:
> >
> > Hi,
> >
> > I believe I found a locally triggerable kernel bug in the IPv6 sendmsg
> > ancillary-data path that can panic the kernel via `skb_under_panic()`
> > (local DoS).
> >
> > The core issue is a mismatch between:
> >
> > - a 16-bit length accumulator (`struct ipv6_txoptions::opt_flen`, type
> > `__u16`) and
> > - a pointer to the *last* provided destination-options header (`opt->dst1opt`)
> >
> > when multiple `IPV6_DSTOPTS` control messages (cmsgs) are provided.
> >
> > ## Affected Area
> >
> > - `include/net/ipv6.h`:
> > - `struct ipv6_txoptions::opt_flen` is `__u16` (wrap possible).
> > (lines 291-307, especially 298)
> > - `net/ipv6/datagram.c:ip6_datagram_send_ctl()`:
> > - Accepts repeated `IPV6_DSTOPTS` and accumulates into `opt_flen`
> > without rejecting duplicates. (lines 909-933)
> > - `net/ipv6/ip6_output.c:__ip6_append_data()`:
> > - Uses `opt->opt_flen + opt->opt_nflen` to compute header
> > sizes/headroom decisions. (lines 1448-1466, especially 1463-1465)
> > - `net/ipv6/ip6_output.c:__ip6_make_skb()`:
> > - Calls `ipv6_push_frag_opts()` if `opt->opt_flen` is non-zero.
> > (lines 1930-1934)
> > - `net/ipv6/exthdrs.c:ipv6_push_frag_opts()` / `ipv6_push_exthdr()`:
> > - Push size comes from `ipv6_optlen(opt->dst1opt)` (based on the
> > pointed-to header). (lines 1179-1185 and 1206-1211)
> >
> > ## Root Cause (Detailed)
> >
> > 1. `opt_flen` is a 16-bit accumulator:
> >
> > - `include/net/ipv6.h:298` defines `__u16 opt_flen; /* after fragment hdr */`.
> >
> > 2. `ip6_datagram_send_ctl()` accepts *repeated* `IPV6_DSTOPTS` cmsgs
> > and increments `opt_flen` each time:
> >
> > - In `net/ipv6/datagram.c:909-933`, for `IPV6_DSTOPTS`:
> > - It computes `len = ((hdr->hdrlen + 1) << 3);`
> > - It checks `CAP_NET_RAW` using `ns_capable(net->user_ns,
> > CAP_NET_RAW)`. (line 922)
> > - Then it does:
> > - `opt->opt_flen += len;` (line 927)
> > - `opt->dst1opt = hdr;` (line 928)
> >
> > There is no duplicate rejection here (unlike the legacy
> > `IPV6_2292DSTOPTS` path which rejects duplicates at
> > `net/ipv6/datagram.c:901-904`).
> >
> > If enough large `IPV6_DSTOPTS` cmsgs are provided, `opt_flen` wraps
> > while `dst1opt` still points to a large (2048-byte)
> > destination-options header.
> >
> > In the attached PoC (`poc.c`):
> >
> > - 32 cmsgs with `hdrlen=255` => `len = (255+1)*8 = 2048`
> > - 1 cmsg with `hdrlen=0` => `len = 8`
> > - Total increment: `32*2048 + 8 = 65544`, so `(__u16)opt_flen == 8`
> > - The last cmsg is 2048 bytes, so `dst1opt` points to a 2048-byte header.
> >
> > 3. The transmit path sizes headers using the wrapped `opt_flen`:
> >
> > - In `net/ipv6/ip6_output.c:1463-1465`:
> > - `headersize = sizeof(struct ipv6hdr) + (opt ? opt->opt_flen +
> > opt->opt_nflen : 0) + ...;`
> >
> > With wrapped `opt_flen`, `headersize`/headroom decisions underestimate
> > what will be pushed later.
> >
> > 4. When building the final skb, the actual push length comes from
> > `dst1opt` and is not limited by wrapped `opt_flen`:
> >
> > - In `net/ipv6/ip6_output.c:1930-1934`:
> > - `if (opt->opt_flen) proto = ipv6_push_frag_opts(skb, opt, proto);`
> > - In `net/ipv6/exthdrs.c:1206-1211`, `ipv6_push_frag_opts()` pushes
> > `dst1opt` via `ipv6_push_exthdr()`.
> > - In `net/ipv6/exthdrs.c:1179-1184`, `ipv6_push_exthdr()` does:
> > - `skb_push(skb, ipv6_optlen(opt));`
> > - `memcpy(h, opt, ipv6_optlen(opt));`
> >
> > With insufficient headroom, `skb_push()` underflows and triggers
> > `skb_under_panic()` -> `BUG()`:
> >
> > - `net/core/skbuff.c:2669-2675` (`skb_push()` calls `skb_under_panic()`)
> > - `net/core/skbuff.c:207-214` (`skb_panic()` ends in `BUG()`)
> >
> > ## Trigger Conditions / Privileges
> >
> > - The `IPV6_DSTOPTS` cmsg path requires `CAP_NET_RAW` in the target
> > netns user namespace (`ns_capable(net->user_ns, CAP_NET_RAW)`).
> > - Root (or any task with `CAP_NET_RAW`) can trigger this without user
> > namespaces.
> > - An unprivileged `uid=1000` user can trigger this if unprivileged
> > user namespaces are enabled and it can create a userns+netns to obtain
> > namespaced `CAP_NET_RAW` (the attached PoC does this).
> >
> > ## Impact
> >
> > - Local denial of service: kernel BUG/panic (system crash).
> > - Reproducible with a small userspace PoC.
> >
> > ## Crash Proof (KASAN Kernel Log Excerpt)
> >
> > This excerpt is from the attached QEMU console log
> > (`qemu_upstream_20260401.log`) captured on a KASAN-enabled kernel (see
> > `kernel.config`, boot args in `kasan_env.txt`):
> >
> > ```text
> > [+] sending IPv6 UDP sendmsg with 33 IPV6_DSTOPTS cmsgs (len=2048 x32, len=8 x1)
> > [*] expect a kernel panic (skb_under_panic) on a vulnerable kernel
> > [ 51.243668] skbuff: skb_under_panic: ... put:2048 ...
> > [ 51.245216] kernel BUG at net/core/skbuff.c:214!
> > [ 51.247204] CPU: 0 UID: 1000 PID: 106 Comm: test ... 7.0.0-rc6+ #1 PREEMPT
> > [ 51.252080] ipv6_push_frag_opts+0x84/0x130
> > [ 51.252349] __ip6_make_skb+0x1773/0x2300
> > [ 51.253728] udpv6_sendmsg+0x1a36/0x2760
> > ...
> > [ 51.267528] Kernel panic - not syncing: Fatal exception
> > ```
> >
> > ## Affected Kernel / Reproduction
> >
> > I reproduced this on Linux upstream commit:
> >
> > - `9147566d801602c9e7fc7f85e989735735bf38ba` (reports as `7.0.0-rc6+`
> > in my build)
> >
> > Reproduction steps (VM recommended; this PoC panics the kernel):
> >
> > 1. Build a kernel with KASAN enabled using the attached `kernel.config`.
> > 2. Boot with the command line in `kasan_env.txt`.
> > 3. Compile and run `poc.c` inside the guest:
> >
> > ```sh
> > gcc -O0 -g -static -fno-stack-protector -o poc poc.c
> > ./poc
> > ```
> >
> > ## Fix Suggestions
> >
> > I think at least one of these should be done; doing (1) and (2)
> > together is most robust:
> >
> > 1. Reject duplicate `IPV6_DSTOPTS` cmsgs, similar to how
> > `IPV6_2292DSTOPTS` rejects duplicates:
> > - If `opt->dst1opt` already set and `cmsg_type == IPV6_DSTOPTS`,
> > return `-EINVAL`.
> >
> > 2. Prevent 16-bit wrap:
> > - Use a wider temporary and reject additions that would
> > overflow/wrap `opt_flen` (e.g., `if (opt->opt_flen > U16_MAX - len)
> > return -EINVAL;` or `check_add_overflow()` with a wide type).
> >
> > 3. Consider widening `opt_flen` / `opt_nflen` to `u32` and validate
> > against sane upper bounds before use.
>
> widening opt_flen would trigger other issues I think because sk_buff
> use u16 offsets.
>
> I will test :
Or smething simpler :
diff --git a/net/ipv6/datagram.c b/net/ipv6/datagram.c
index c564b68a056268c7cbc81b5f29f60289ea9e09eb..2ace04b318f34bf4a5cd890f347d03193f3af7d1
100644
--- a/net/ipv6/datagram.c
+++ b/net/ipv6/datagram.c
@@ -924,9 +924,13 @@ int ip6_datagram_send_ctl(struct net *net, struct sock *sk,
goto exit_f;
}
if (cmsg->cmsg_type == IPV6_DSTOPTS) {
+ if (opt->dst1opt)
+ opt->opt_flen -=
ipv6_optlen(opt->dst1opt);
opt->opt_flen += len;
opt->dst1opt = hdr;
} else {
+ if (opt->dst0opt)
+ opt->opt_nflen -=
ipv6_optlen(opt->dst0opt);
opt->opt_nflen += len;
opt->dst0opt = hdr;
}
^ permalink raw reply
* Re: [PATCH net-next v2 6/9] net: dsa: lan9645x: add vlan support
From: Jens Emil Schulz Ostergaard @ 2026-04-01 14:44 UTC (permalink / raw)
To: Jakub Kicinski
Cc: UNGLinuxDriver, andrew, olteanv, davem, edumazet, pabeni, horms,
robh, krzk+dt, conor+dt, woojung.huh, linux, Steen.Hegelund,
daniel.machon, linux-kernel, netdev, devicetree
In-Reply-To: <20260329195633.2789187-1-kuba@kernel.org>
On Sun, 2026-03-29 at 12:56 -0700, Jakub Kicinski wrote:
> EXTERNAL EMAIL: Do not click links or open attachments unless you know the content is safe
>
> This is an AI-generated review of your patch. The human sending this
> email has considered the AI review valid, or at least plausible.
> ---
> net: dsa: lan9645x: add vlan support
>
> Add support for vlanaware bridge. We reserve vid 4095 for standalone
> mode, to implement fdb-isolation. A vlan-unaware bridge uses vid 0.
>
> > diff --git a/drivers/net/dsa/microchip/lan9645x/lan9645x_main.c b/drivers/net/dsa/microchip/lan9645x/lan9645x_main.c
> > index 599e589c4ec3..046c95a72242 100644
> > --- a/drivers/net/dsa/microchip/lan9645x/lan9645x_main.c
> > +++ b/drivers/net/dsa/microchip/lan9645x/lan9645x_main.c
> [ ... ]
> > @@ -535,11 +536,54 @@ static void lan9645x_port_bridge_leave(struct dsa_switch *ds, int port,
> [ ... ]
> > +static int lan9645x_port_vlan_filtering(struct dsa_switch *ds, int port,
> > + bool enabled,
> > + struct netlink_ext_ack *extack)
> > +{
> > + struct lan9645x *lan9645x = ds->priv;
> > + struct lan9645x_port *p;
> > +
> > + p = lan9645x_to_port(lan9645x, port);
> > + p->vlan_aware = enabled;
> > + lan9645x_vlan_port_apply(p);
>
> In other places, the driver avoids calling egress functions on the npi
> port because it conflicts with cpu port header extraction. Since this
> unconditionally applies the vlan configuration to both ingress and egress,
> could this overwrite the npi port's configuration when the state is
> synchronized?
>
It is true we avoid this, but the vlan_filtering is not called by DSA core for
the cpu port, only user ports.
> > +
> > + return 0;
> > +}
> > +
> > +static int lan9645x_port_vlan_add(struct dsa_switch *ds, int port,
> [ ... ]
> > diff --git a/drivers/net/dsa/microchip/lan9645x/lan9645x_vlan.c b/drivers/net/dsa/microchip/lan9645x/lan9645x_vlan.c
> > new file mode 100644
> > index 000000000000..0459932a626c
> > --- /dev/null
> > +++ b/drivers/net/dsa/microchip/lan9645x/lan9645x_vlan.c
> [ ... ]
> > +static void lan9645x_vlan_hw_wr(struct lan9645x *lan9645x, u16 vid)
> > +{
> > + struct lan9645x_vlan *v = &lan9645x->vlans[vid];
> > + bool cpu_dis = !(v->portmask & BIT(CPU_PORT));
> > + u32 val;
> > +
> > + val = ANA_VLANTIDX_VLAN_PGID_CPU_DIS_SET(cpu_dis) |
> > + ANA_VLANTIDX_V_INDEX_SET(vid) |
> > + ANA_VLANTIDX_VLAN_SEC_FWD_ENA_SET(v->s_fwd_ena) |
> > + ANA_VLANTIDX_VLAN_FLOOD_DIS_SET(v->fld_dis) |
> > + ANA_VLANTIDX_VLAN_PRIV_VLAN_SET(v->prv_vlan) |
> > + ANA_VLANTIDX_VLAN_LEARN_DISABLED_SET(v->lrn_dis) |
> > + ANA_VLANTIDX_VLAN_MIRROR_SET(v->mir) |
> > + ANA_VLANTIDX_VLAN_SRC_CHK_SET(v->src_chk);
> > +
> > + lan_wr(val, lan9645x, ANA_VLANTIDX);
> > + lan_wr(v->portmask, lan9645x, ANA_VLAN_PORT_MASK);
> > + lan_wr(VLANACCESS_CMD_WRITE, lan9645x, ANA_VLANACCESS);
>
> Are these registers written with raw values rather than using their respective
> set macros? This circumvents the register abstraction and might risk failures
> if the hardware layout changes.
>
Yes, they are single field registers. I can add the macro.
> > +
> > + if (lan9645x_vlan_wait_for_completion(lan9645x))
> > + dev_err(lan9645x->dev, "Vlan set mask failed\n");
> > +}
> [ ... ]
> > +static void
> > +lan9645x_vlan_port_apply_egress(struct lan9645x_port *p,
> > + struct lan9645x_vlan_port_info *info)
> > +{
> [ ... ]
> > + lan_rmw(REW_TAG_CFG_TAG_TPID_CFG_SET(3) |
> > + REW_TAG_CFG_TAG_CFG_SET(tag_cfg),
> > + REW_TAG_CFG_TAG_TPID_CFG |
> > + REW_TAG_CFG_TAG_CFG,
> > + lan9645x, REW_TAG_CFG(p->chip_port));
> > +
> > + lan_rmw(REW_PORT_VLAN_CFG_PORT_TPID_SET(ETH_P_8021AD) |
> > + REW_PORT_VLAN_CFG_PORT_VID_SET(port_vid),
> > + REW_PORT_VLAN_CFG_PORT_TPID |
> > + REW_PORT_VLAN_CFG_PORT_VID,
> > + lan9645x, REW_PORT_VLAN_CFG(p->chip_port));
> > +}
>
> Setting the port tpid to 0x88a8 causes untagged ingress frames to egress trunk
> ports with 802.1ad tags. Could this break interoperability with standard
> networks that expect 0x8100 tags?
>
No that is not how it works. I think the comment which is left out explains this
clearly. 0x8100 tags are unaffected by this, but it means an stag is not rewritten
to a ctag.
> > +static void lan9645x_vlan_port_apply_ingress(struct lan9645x_port *p)
> > +{
> [ ... ]
> > + /* Drop frames with multicast source address */
> > + val = ANA_DROP_CFG_DROP_MC_SMAC_ENA_SET(1);
> > + if (p->vlan_aware && !pvid)
> > + /* If port is vlan-aware and tagged, drop untagged and priority
> > + * tagged frames.
> > + */
> > + val |= ANA_DROP_CFG_DROP_UNTAGGED_ENA_SET(1) |
> > + ANA_DROP_CFG_DROP_PRIO_S_TAGGED_ENA_SET(1) |
> > + ANA_DROP_CFG_DROP_PRIO_C_TAGGED_ENA_SET(1);
> > +
> > + lan_wr(val, lan9645x, ANA_DROP_CFG(p->chip_port));
> > +}
>
> Does this overwrite the entire 32-bit register and silently clear other
> initialized bitfields? Would it be safer to use a read-modify-write here
> instead?
>
Yes this is intentional. I prefer this over writing all fields with rmw which is
slower.
> [ ... ]
> > +static int lan9645x_vlan_cpu_add(struct lan9645x_port *p, u16 vid, bool pvid,
> > + bool untagged)
> > +{
> > + struct lan9645x_vlan *v;
> > +
> > + v = lan9645x_vlan_port_modify(p, vid, pvid, untagged);
>
> Is it intentional that this bypasses the bounds check on the vlan id?
>
No, I will move the cpu action below the bounds check.
> > + v->portmask |= BIT(CPU_PORT) | BIT(p->chip_port);
> > + lan9645x_vlan_hw_wr(p->lan9645x, vid);
> > + lan9645x_vlan_port_apply_ingress(p);
> > +
> > + return 0;
> > +}
> > +
> > +int lan9645x_vlan_port_add_vlan(struct lan9645x_port *p, u16 vid, bool pvid,
> > + bool untagged, struct netlink_ext_ack *extack)
> > +{
> [ ... ]
> > +void lan9645x_vlan_port_del_vlan(struct lan9645x_port *p, u16 vid)
> > +{
> > + struct lan9645x *lan9645x = p->lan9645x;
> > + struct lan9645x_vlan *v;
> > +
> > + if (!vid)
> > + return;
> > +
> > + v = lan9645x_vlan_port_modify(p, vid, false, false);
>
> There is a check for the maximum vlan id when adding a vlan, but no bounds
> check when deleting one. Could userspace use this to delete the reserved
> vid 4095 and break standalone forwarding?
>
I will add the bounds check for delete.
> > + v->portmask &= ~BIT(p->chip_port);
> > +
> > + if (p->chip_port == lan9645x->npi)
> > + v->portmask &= ~BIT(CPU_PORT);
> > +
> > + lan9645x_vlan_hw_wr(lan9645x, vid);
> [ ... ]
> > +void lan9645x_vlan_init(struct lan9645x *lan9645x)
> > +{
> [ ... ]
> > + for (vid = 1; vid < VLAN_N_VID; vid++)
> > + lan9645x_vlan_hw_wr(lan9645x, vid);
>
> If the indirect table access times out, the write function prints an error
> but does not return a status code. Will this cause the initialization loop
> to stall for the full timeout duration 4095 consecutive times if the
> hardware is unresponsive?
>
Yes, I can return the err from vlan_hw_wr and return it early from vlan_init.
> > +
> > + /* Set all the ports + cpu to be part of HOST_PVID and UNAWARE_PVID */
> > + lan9645x->vlans[HOST_PVID].portmask = all_ports;
^ permalink raw reply
* Re: [PATCH RESEND net v2 1/1] net/sched: sch_netem: fix out-of-bounds access in packet corruption
From: Stephen Hemminger @ 2026-04-01 14:46 UTC (permalink / raw)
To: Yucheng Lu
Cc: security, netdev, jhs, jiri, davem, Jason, kees, yifanwucs,
tomapufckgml, tanyuan98, bird, z1652074432
In-Reply-To: <45435c0935df877853a81e6d06205ac738ec65fa.1774941614.git.kanolyc@gmail.com>
On Tue, 31 Mar 2026 16:00:21 +0800
Yucheng Lu <kanolyc@gmail.com> wrote:
> In netem_enqueue(), the packet corruption logic uses
> get_random_u32_below(skb_headlen(skb)) to select an index for
> modifying skb->data. When an AF_PACKET TX_RING sends fully non-linear
> packets over an IPIP tunnel, skb_headlen(skb) evaluates to 0.
>
> Passing 0 to get_random_u32_below() takes the variable-ceil slow path
> which returns an unconstrained 32-bit random integer. Using this
> unconstrained value as an offset into skb->data results in an
> out-of-bounds memory access.
>
> Fix this by verifying skb_headlen(skb) is non-zero before attempting
> to corrupt the linear data area. Fully non-linear packets will silently
> bypass the corruption logic.
>
> Fixes: c865e5d99e25 ("[PKT_SCHED] netem: packet corruption option")
> Reported-by: Yifan Wu <yifanwucs@gmail.com>
> Reported-by: Juefei Pu <tomapufckgml@gmail.com>
> Signed-off-by: Yuan Tan <tanyuan98@outlook.com>
> Signed-off-by: Xin Liu <bird@lzu.edu.cn>
> Signed-off-by: Yuhang Zheng <z1652074432@gmail.com>
> Signed-off-by: Yucheng Lu <kanolyc@gmail.com>
> ---
This is the correct fix for the net tree. Will make a more robust
fix for net-next which handles non linear packets better.
Reviewed-by: Stephen Hemminger <stephen@networkplumber.org>
^ permalink raw reply
* Re: [PATCH net-next v4 1/2] net: hsr: require valid EOT supervision TLV
From: Fernando Fernandez Mancera @ 2026-04-01 14:47 UTC (permalink / raw)
To: luka.gejak, davem, edumazet, kuba, pabeni; +Cc: netdev, fmaurer, horms
In-Reply-To: <20260401092324.52266-2-luka.gejak@linux.dev>
On 4/1/26 11:23 AM, luka.gejak@linux.dev wrote:
> From: Luka Gejak <luka.gejak@linux.dev>
>
> Supervision frames are only valid if terminated with a zero-length EOT
> TLV. The current check fails to reject non-EOT entries as the terminal
> TLV, potentially allowing malformed supervision traffic.
>
> Fix this by strictly requiring the terminal TLV to be HSR_TLV_EOT
> with a length of zero.
>
> Reviewed-by: Felix Maurer <fmaurer@redhat.com>
> Signed-off-by: Luka Gejak <luka.gejak@linux.dev>
> ---
> net/hsr/hsr_forward.c | 41 ++++++++++++++++++++++-------------------
> 1 file changed, 22 insertions(+), 19 deletions(-)
>
> diff --git a/net/hsr/hsr_forward.c b/net/hsr/hsr_forward.c
> index 0aca859c88cb..17b705235c4a 100644
> --- a/net/hsr/hsr_forward.c
> +++ b/net/hsr/hsr_forward.c
> @@ -82,39 +82,42 @@ static bool is_supervision_frame(struct hsr_priv *hsr, struct sk_buff *skb)
> hsr_sup_tag->tlv.HSR_TLV_length != sizeof(struct hsr_sup_payload))
> return false;
>
> - /* Get next tlv */
> + /* Advance past the first TLV payload to reach next TLV header */
> total_length += hsr_sup_tag->tlv.HSR_TLV_length;
> - if (!pskb_may_pull(skb, total_length))
> + /* Linearize next TLV header before access */
> + if (!pskb_may_pull(skb, total_length + sizeof(struct hsr_sup_tlv)))
> return false;
> skb_pull(skb, total_length);
> hsr_sup_tlv = (struct hsr_sup_tlv *)skb->data;
> skb_push(skb, total_length);
>
> - /* if this is a redbox supervision frame we need to verify
> - * that more data is available
> + /* Walk through TLVs to find end-of-TLV marker, skipping any unknown
> + * extension TLVs to maintain forward compatibility.
> */
> - if (hsr_sup_tlv->HSR_TLV_type == PRP_TLV_REDBOX_MAC) {
> - /* tlv length must be a length of a mac address */
> - if (hsr_sup_tlv->HSR_TLV_length != sizeof(struct hsr_sup_payload))
> - return false;
> + for (;;) {
> + if (hsr_sup_tlv->HSR_TLV_type == HSR_TLV_EOT &&
> + hsr_sup_tlv->HSR_TLV_length == 0)
> + return true;
>
I do not follow this approach, why a loop? From IEC 62439-3, I do not
understand that supervision frames could have multiple
PRP_TLV_REDBOX_MAC TLVs. The current code handles the TLVs correctly.
Which makes me wonder, how are you testing this? Do you have some
hardware with HSR/PRP support that is sending these frames? If so, which
one? Are you testing this using a HSR/PRP environment with purely Linux
devices?
Thanks,
Fernando.
> - /* make sure another tlv follows */
> - total_length += sizeof(struct hsr_sup_tlv) + hsr_sup_tlv->HSR_TLV_length;
> - if (!pskb_may_pull(skb, total_length))
> + /* Validate known TLV types */
> + if (hsr_sup_tlv->HSR_TLV_type == PRP_TLV_REDBOX_MAC) {
> + if (hsr_sup_tlv->HSR_TLV_length !=
> + sizeof(struct hsr_sup_payload))
> + return false;
> + }
> +
> + /* Advance past current TLV: header + payload */
> + total_length += sizeof(struct hsr_sup_tlv) +
> + hsr_sup_tlv->HSR_TLV_length;
> + /* Linearize next TLV header before access */
> + if (!pskb_may_pull(skb,
> + total_length + sizeof(struct hsr_sup_tlv)))
> return false;
>
> - /* get next tlv */
> skb_pull(skb, total_length);
> hsr_sup_tlv = (struct hsr_sup_tlv *)skb->data;
> skb_push(skb, total_length);
> }
^ permalink raw reply
* Re: [PATCH net-next v3 2/3] dpll: add frequency monitoring callback ops
From: Vadim Fedorenko @ 2026-04-01 14:47 UTC (permalink / raw)
To: Ivan Vecera, netdev
Cc: Arkadiusz Kubalewski, David S. Miller, Donald Hunter,
Eric Dumazet, Jakub Kicinski, Jiri Pirko, Jonathan Corbet,
Michal Schmidt, Paolo Abeni, Petr Oros, Prathosh Satish,
Shuah Khan, Simon Horman, linux-doc, linux-kernel
In-Reply-To: <20260401091237.1071995-3-ivecera@redhat.com>
On 01/04/2026 10:12, Ivan Vecera wrote:
> Add new callback operations for a dpll device:
> - freq_monitor_get(..) - to obtain current state of frequency monitor
> feature from dpll device,
> - freq_monitor_set(..) - to allow feature configuration.
>
> Add new callback operation for a dpll pin:
> - measured_freq_get(..) - to obtain the measured frequency in mHz.
>
> Obtain the feature state value using the get callback and provide it to
> the user if the device driver implements callbacks. The measured_freq_get
> pin callback is only invoked when the frequency monitor is enabled.
> The freq_monitor_get device callback is required when measured_freq_get
> is provided by the driver.
>
> Execute the set callback upon user requests.
>
> Reviewed-by: Vadim Fedorenko <vadim.fedorenko@linux.dev>
> Signed-off-by: Ivan Vecera <ivecera@redhat.com>
> ---
> Changes v2 -> v3:
> - Made freq_monitor_get required when measured_freq_get is present (Jakub)
>
> Changes v1 -> v2:
> - Renamed actual-frequency to measured-frequency (Vadim)
> ---
> drivers/dpll/dpll_netlink.c | 92 +++++++++++++++++++++++++++++++++++++
> include/linux/dpll.h | 10 ++++
> 2 files changed, 102 insertions(+)
>
> diff --git a/drivers/dpll/dpll_netlink.c b/drivers/dpll/dpll_netlink.c
> index 83cbd64abf5a4..576d0cd074bd4 100644
> --- a/drivers/dpll/dpll_netlink.c
> +++ b/drivers/dpll/dpll_netlink.c
> @@ -175,6 +175,26 @@ dpll_msg_add_phase_offset_monitor(struct sk_buff *msg, struct dpll_device *dpll,
> return 0;
> }
>
> +static int
> +dpll_msg_add_freq_monitor(struct sk_buff *msg, struct dpll_device *dpll,
> + struct netlink_ext_ack *extack)
> +{
> + const struct dpll_device_ops *ops = dpll_device_ops(dpll);
> + enum dpll_feature_state state;
> + int ret;
> +
> + if (ops->freq_monitor_set && ops->freq_monitor_get) {
> + ret = ops->freq_monitor_get(dpll, dpll_priv(dpll),
> + &state, extack);
> + if (ret)
> + return ret;
> + if (nla_put_u32(msg, DPLL_A_FREQUENCY_MONITOR, state))
> + return -EMSGSIZE;
> + }
> +
> + return 0;
> +}
> +
> static int
> dpll_msg_add_phase_offset_avg_factor(struct sk_buff *msg,
> struct dpll_device *dpll,
> @@ -400,6 +420,40 @@ static int dpll_msg_add_ffo(struct sk_buff *msg, struct dpll_pin *pin,
> ffo);
> }
>
> +static int dpll_msg_add_measured_freq(struct sk_buff *msg, struct dpll_pin *pin,
> + struct dpll_pin_ref *ref,
> + struct netlink_ext_ack *extack)
> +{
> + const struct dpll_device_ops *dev_ops = dpll_device_ops(ref->dpll);
> + const struct dpll_pin_ops *ops = dpll_pin_ops(ref);
> + struct dpll_device *dpll = ref->dpll;
> + enum dpll_feature_state state;
> + u64 measured_freq;
> + int ret;
> +
> + if (!ops->measured_freq_get)
> + return 0;
> + if (WARN_ON(!dev_ops->freq_monitor_get))
> + return -EINVAL;
I think pin registration function has to be adjusted to not allow
measured_freq_get() callback if device doesn't have freq_monitor_get()
callback (or both freq_monitor_{s,g}et). Then this defensive part can
be completely removed.
> + ret = dev_ops->freq_monitor_get(dpll, dpll_priv(dpll),
^ permalink raw reply
* Re: [PATCH 2/2 net v2] ipv4: nexthop: allocate skb dynamically in rtm_get_nexthop()
From: Fernando Fernandez Mancera @ 2026-04-01 14:50 UTC (permalink / raw)
To: netdev
Cc: idosch, petrm, horms, pabeni, kuba, edumazet, davem, dsahern,
kees, Yiming Qian
In-Reply-To: <20260401081741.4273-2-fmancera@suse.de>
On 4/1/26 10:17 AM, Fernando Fernandez Mancera wrote:
> When querying a nexthop object via RTM_GETNEXTHOP, the kernel currently
> allocates a fixed-size skb using NLMSG_GOODSIZE. While sufficient for
> single nexthops and small Equal-Cost Multi-Path groups, this fixed
> allocation fails for large nexthop groups like 512 nexthops.
>
> This results in the following warning splat:
>
> WARNING: net/ipv4/nexthop.c:3395 at rtm_get_nexthop+0x176/0x1c0, CPU#20: rep/4608
> [...]
> RIP: 0010:rtm_get_nexthop (net/ipv4/nexthop.c:3395)
> [...]
> Call Trace:
> <TASK>
> rtnetlink_rcv_msg (net/core/rtnetlink.c:6989)
> netlink_rcv_skb (net/netlink/af_netlink.c:2550)
> netlink_unicast (net/netlink/af_netlink.c:1319 net/netlink/af_netlink.c:1344)
> netlink_sendmsg (net/netlink/af_netlink.c:1894)
> ____sys_sendmsg (net/socket.c:721 net/socket.c:736 net/socket.c:2585)
> ___sys_sendmsg (net/socket.c:2641)
> __sys_sendmsg (net/socket.c:2671)
> do_syscall_64 (arch/x86/entry/syscall_64.c:63 arch/x86/entry/syscall_64.c:94)
> entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:130)
> </TASK>
>
> Fix this by allocating the size dynamically using nh_nlmsg_size() and
> using nlmsg_new(), this is consistent with nexthop_notify() behavior. In
> addition, adjust nh_nlmsg_size_grp() so it calculates the size needed
> based on flags passed.
>
> This cannot be reproduced via iproute2 as the group size is currently
> limited and the command fails as follows:
>
> addattr_l ERROR: message exceeded bound of 1048
>
> Fixes: 430a049190de ("nexthop: Add support for nexthop groups")
> Reported-by: Yiming Qian <yimingqian591@gmail.com>
> Closes: https://lore.kernel.org/netdev/CAL_bE8Li2h4KO+AQFXW4S6Yb_u5X4oSKnkywW+LPFjuErhqELA@mail.gmail.com/
> Signed-off-by: Fernando Fernandez Mancera <fmancera@suse.de>
As Ido requested some changes, update the status in patchwork.
--
pw-bot: cr
^ permalink raw reply
* [PATCH net 0/7] net/sched: netem: bug fixes
From: Stephen Hemminger @ 2026-04-01 14:51 UTC (permalink / raw)
To: netdev; +Cc: Stephen Hemminger
These bugs were identified while using AI-assisted code review of
sch_netem.c to analyze the packet duplication re-entrancy problem
(CVE-2025-37890, CVE-2025-38001), which are addressed in a separate
series.
The review uncovered several additional issues:
- probability gaps in the 4-state Markov loss model where
boundary values produce no state transition
- queue limit check not accounting for reordered packets
- PRNG reseeded on every tc change, breaking reproducibility
- the core dequeue re-entrancy issue with child qdiscs
causing HFSC eltree corruption and DRR class stalls
- missing NULL termination on the tfifo linear list tail
- slot delay configuration not validated for inverted ranges
- slot delay arithmetic overflow for ranges above ~2.1 seconds
v2 - pickup the slot related bug fixes
Stephen Hemminger (7):
net/sched: netem: fix probability gaps in 4-state loss model
net/sched: netem: fix queue limit check to include reordered packets
net/sched: netem: only reseed PRNG when seed is explicitly provided
net/sched: netem: restructure dequeue to avoid re-entrancy with child
qdisc
net/sched: netem: null-terminate tfifo linear queue tail
net/sched: netem: check for invalid slot range
net/sched: netem: fix slot delay calculation overflow
net/sched/sch_netem.c | 244 +++++++++++++++++++++++++++---------------
1 file changed, 159 insertions(+), 85 deletions(-)
--
2.53.0
^ permalink raw reply
* [PATCH net v2 1/7] net/sched: netem: fix probability gaps in 4-state loss model
From: Stephen Hemminger @ 2026-04-01 14:51 UTC (permalink / raw)
To: netdev
Cc: Stephen Hemminger, Jamal Hadi Salim, Jiri Pirko, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
open list
In-Reply-To: <20260401145332.78285-1-stephen@networkplumber.org>
The 4-state Markov chain in loss_4state() has gaps at the boundaries
between transition probability ranges. The comparisons use:
if (rnd < a4)
else if (a4 < rnd && rnd < a1 + a4)
When rnd equals a boundary value exactly, neither branch matches and
no state transition occurs. The redundant lower-bound check (a4 < rnd)
is already implied by being in the else branch.
Remove the unnecessary lower-bound comparisons so the ranges are
contiguous and every random value produces a transition, matching
the GI (General and Intuitive) loss model specification.
This bug goes back to original implementation of this model.
Fixes: 661b79725fea ("netem: revised correlated loss generator")
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
net/sched/sch_netem.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/net/sched/sch_netem.c b/net/sched/sch_netem.c
index 5de1c932944a..2cc3acaa4068 100644
--- a/net/sched/sch_netem.c
+++ b/net/sched/sch_netem.c
@@ -227,10 +227,10 @@ static bool loss_4state(struct netem_sched_data *q)
if (rnd < clg->a4) {
clg->state = LOST_IN_GAP_PERIOD;
return true;
- } else if (clg->a4 < rnd && rnd < clg->a1 + clg->a4) {
+ } else if (rnd < clg->a1 + clg->a4) {
clg->state = LOST_IN_BURST_PERIOD;
return true;
- } else if (clg->a1 + clg->a4 < rnd) {
+ } else {
clg->state = TX_IN_GAP_PERIOD;
}
@@ -247,9 +247,9 @@ static bool loss_4state(struct netem_sched_data *q)
case LOST_IN_BURST_PERIOD:
if (rnd < clg->a3)
clg->state = TX_IN_BURST_PERIOD;
- else if (clg->a3 < rnd && rnd < clg->a2 + clg->a3) {
+ else if (rnd < clg->a2 + clg->a3) {
clg->state = TX_IN_GAP_PERIOD;
- } else if (clg->a2 + clg->a3 < rnd) {
+ } else {
clg->state = LOST_IN_BURST_PERIOD;
return true;
}
--
2.53.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox