Netdev List
 help / color / mirror / Atom feed
* [PATCH iwl-next 2/2] i40e: keep track of per queue gso counters.
From: Paolo Abeni @ 2026-04-08 11:43 UTC (permalink / raw)
  To: intel-wired-lan
  Cc: Tony Nguyen, Przemek Kitszel, Andrew Lunn, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Alexei Starovoitov, Daniel Borkmann,
	Jesper Dangaard Brouer, John Fastabend, Stanislav Fomichev,
	netdev
In-Reply-To: <cover.1775648513.git.pabeni@redhat.com>

Track the number of GSO and wire packets transmitted and expose the
counters via the queue stats.

Signed-off-by: Paolo Abeni <pabeni@redhat.com>
---
 drivers/net/ethernet/intel/i40e/i40e.h             |  2 ++
 drivers/net/ethernet/intel/i40e/i40e_main.c        | 13 ++++++++++++-
 drivers/net/ethernet/intel/i40e/i40e_txrx.c        |  8 +++++++-
 drivers/net/ethernet/intel/i40e/i40e_txrx.h        |  2 ++
 drivers/net/ethernet/intel/i40e/i40e_txrx_common.h |  6 +++++-
 drivers/net/ethernet/intel/i40e/i40e_xsk.c         |  2 +-
 6 files changed, 29 insertions(+), 4 deletions(-)

diff --git a/drivers/net/ethernet/intel/i40e/i40e.h b/drivers/net/ethernet/intel/i40e/i40e.h
index fe642c464e9c..4a88c7d69f61 100644
--- a/drivers/net/ethernet/intel/i40e/i40e.h
+++ b/drivers/net/ethernet/intel/i40e/i40e.h
@@ -845,6 +845,8 @@ struct i40e_vsi {
 	u64 tx_stopped_base;
 	u64 tx_bytes;
 	u64 tx_packets;
+	u64 tx_gso_packets;
+	u64 tx_gso_wire_packets;
 	u64 rx_buf_failed;
 	u64 rx_page_failed;
 	u64 rx_page_reuse;
diff --git a/drivers/net/ethernet/intel/i40e/i40e_main.c b/drivers/net/ethernet/intel/i40e/i40e_main.c
index 0d0b3619ec56..7c2af738ccc5 100644
--- a/drivers/net/ethernet/intel/i40e/i40e_main.c
+++ b/drivers/net/ethernet/intel/i40e/i40e_main.c
@@ -11695,6 +11695,8 @@ static void i40e_vsi_aggregate_tx_counters(struct i40e_vsi *vsi,
 
 	vsi->tx_bytes += tx_ring->stats.bytes;
 	vsi->tx_packets += tx_ring->stats.packets;
+	vsi->tx_gso_packets += tx_ring->tx_stats.tx_gso_packets;
+	vsi->tx_gso_wire_packets += tx_ring->tx_stats.tx_gso_wire_packets;
 }
 
 static void i40e_vsi_aggregate_rx_counters(struct i40e_vsi *vsi,
@@ -13684,6 +13686,8 @@ static void i40e_zero_tx_ring_stats(struct netdev_queue_stats_tx *tx)
 {
 	tx->bytes = 0;
 	tx->packets = 0;
+	tx->hw_gso_packets = 0;
+	tx->hw_gso_wire_packets = 0;
 	tx->stop = 0;
 	tx->wake = 0;
 	tx->hw_drops = 0;
@@ -13692,17 +13696,21 @@ static void i40e_zero_tx_ring_stats(struct netdev_queue_stats_tx *tx)
 static void i40e_add_tx_ring_stats(struct i40e_ring *tx_ring,
 				   struct netdev_queue_stats_tx *tx)
 {
-	u64 bytes, packets;
+	u64 bytes, packets, gso_packets, gso_wire_packets;
 	unsigned int start;
 
 	do {
 		start = u64_stats_fetch_begin(&tx_ring->syncp);
 		bytes = tx_ring->stats.bytes;
 		packets = tx_ring->stats.packets;
+		gso_packets = tx_ring->tx_stats.tx_gso_packets;
+		gso_wire_packets = tx_ring->tx_stats.tx_gso_wire_packets;
 	} while (u64_stats_fetch_retry(&tx_ring->syncp, start));
 
 	tx->bytes += bytes;
 	tx->packets += packets;
+	tx->hw_gso_packets += gso_packets;
+	tx->hw_gso_wire_packets += gso_wire_packets;
 
 	tx->stop += tx_ring->tx_stats.tx_stopped;
 	tx->wake += tx_ring->tx_stats.restart_queue;
@@ -13743,6 +13751,9 @@ static void i40e_get_base_stats(struct net_device *dev,
 
 	tx->bytes = vsi->tx_bytes;
 	tx->packets = vsi->tx_packets;
+	tx->hw_gso_packets = vsi->tx_gso_packets;
+	tx->hw_gso_wire_packets = vsi->tx_gso_wire_packets;
+
 	tx->wake = vsi->tx_restart_base;
 	tx->stop = vsi->tx_stopped_base;
 	tx->hw_drops = vsi->tx_busy_base;
diff --git a/drivers/net/ethernet/intel/i40e/i40e_txrx.c b/drivers/net/ethernet/intel/i40e/i40e_txrx.c
index 894f2d06d39d..ec61cc43ae39 100644
--- a/drivers/net/ethernet/intel/i40e/i40e_txrx.c
+++ b/drivers/net/ethernet/intel/i40e/i40e_txrx.c
@@ -928,6 +928,7 @@ static bool i40e_clean_tx_irq(struct i40e_vsi *vsi,
 			      struct i40e_ring *tx_ring, int napi_budget,
 			      unsigned int *tx_cleaned)
 {
+	unsigned int total_gso_packets = 0, total_gso_wire_packets = 0;
 	int i = tx_ring->next_to_clean;
 	struct i40e_tx_buffer *tx_buf;
 	struct i40e_tx_desc *tx_head;
@@ -959,6 +960,10 @@ static bool i40e_clean_tx_irq(struct i40e_vsi *vsi,
 		/* update the statistics for this packet */
 		total_bytes += tx_buf->bytecount;
 		total_packets += tx_buf->gso_segs;
+		if (tx_buf->gso_segs > 1) {
+			total_gso_packets++;
+			total_gso_wire_packets += tx_buf->gso_segs;
+		}
 
 		/* free the skb/XDP data */
 		if (ring_is_xdp(tx_ring))
@@ -1018,7 +1023,8 @@ static bool i40e_clean_tx_irq(struct i40e_vsi *vsi,
 
 	i += tx_ring->count;
 	tx_ring->next_to_clean = i;
-	i40e_update_tx_stats(tx_ring, total_packets, total_bytes);
+	i40e_update_tx_stats(tx_ring, total_packets, total_bytes,
+			     total_gso_packets, total_gso_wire_packets);
 	i40e_arm_wb(tx_ring, vsi, budget);
 
 	if (ring_is_xdp(tx_ring))
diff --git a/drivers/net/ethernet/intel/i40e/i40e_txrx.h b/drivers/net/ethernet/intel/i40e/i40e_txrx.h
index 1e5fd63d47f4..15c608378467 100644
--- a/drivers/net/ethernet/intel/i40e/i40e_txrx.h
+++ b/drivers/net/ethernet/intel/i40e/i40e_txrx.h
@@ -295,6 +295,8 @@ struct i40e_tx_queue_stats {
 	u64 tx_linearize;
 	u64 tx_force_wb;
 	u64 tx_stopped;
+	u64 tx_gso_packets;
+	u64 tx_gso_wire_packets;
 	int prev_pkt_ctr;
 };
 
diff --git a/drivers/net/ethernet/intel/i40e/i40e_txrx_common.h b/drivers/net/ethernet/intel/i40e/i40e_txrx_common.h
index e26807fd2123..24220594ff1a 100644
--- a/drivers/net/ethernet/intel/i40e/i40e_txrx_common.h
+++ b/drivers/net/ethernet/intel/i40e/i40e_txrx_common.h
@@ -45,11 +45,15 @@ static inline __le64 build_ctob(u32 td_cmd, u32 td_offset, unsigned int size,
  **/
 static inline void i40e_update_tx_stats(struct i40e_ring *tx_ring,
 					unsigned int total_packets,
-					unsigned int total_bytes)
+					unsigned int total_bytes,
+					unsigned int total_gso,
+					unsigned int total_gso_wire)
 {
 	u64_stats_update_begin(&tx_ring->syncp);
 	tx_ring->stats.bytes += total_bytes;
 	tx_ring->stats.packets += total_packets;
+	tx_ring->tx_stats.tx_gso_packets += total_gso;
+	tx_ring->tx_stats.tx_gso_wire_packets += total_gso_wire;
 	u64_stats_update_end(&tx_ring->syncp);
 	tx_ring->q_vector->tx.total_bytes += total_bytes;
 	tx_ring->q_vector->tx.total_packets += total_packets;
diff --git a/drivers/net/ethernet/intel/i40e/i40e_xsk.c b/drivers/net/ethernet/intel/i40e/i40e_xsk.c
index 9f47388eaba5..c2a465416a33 100644
--- a/drivers/net/ethernet/intel/i40e/i40e_xsk.c
+++ b/drivers/net/ethernet/intel/i40e/i40e_xsk.c
@@ -599,7 +599,7 @@ static bool i40e_xmit_zc(struct i40e_ring *xdp_ring, unsigned int budget)
 	i40e_set_rs_bit(xdp_ring);
 	i40e_xdp_ring_update_tail(xdp_ring);
 
-	i40e_update_tx_stats(xdp_ring, nb_pkts, total_bytes);
+	i40e_update_tx_stats(xdp_ring, nb_pkts, total_bytes, 0, 0);
 
 	return nb_pkts < budget;
 }
-- 
2.53.0


^ permalink raw reply related

* [PATCH iwl-next 1/2] i40e: implement basic per-queue stats
From: Paolo Abeni @ 2026-04-08 11:43 UTC (permalink / raw)
  To: intel-wired-lan
  Cc: Tony Nguyen, Przemek Kitszel, Andrew Lunn, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Alexei Starovoitov, Daniel Borkmann,
	Jesper Dangaard Brouer, John Fastabend, Stanislav Fomichev,
	netdev
In-Reply-To: <cover.1775648513.git.pabeni@redhat.com>

Only expose the counters currently available (bytes, packets); add
account for base stats to deal with ring clear.

Signed-off-by: Paolo Abeni <pabeni@redhat.com>
---
 drivers/net/ethernet/intel/i40e/i40e.h      |   7 ++
 drivers/net/ethernet/intel/i40e/i40e_main.c | 133 ++++++++++++++++++++
 2 files changed, 140 insertions(+)

diff --git a/drivers/net/ethernet/intel/i40e/i40e.h b/drivers/net/ethernet/intel/i40e/i40e.h
index dcb50c2e1aa2..fe642c464e9c 100644
--- a/drivers/net/ethernet/intel/i40e/i40e.h
+++ b/drivers/net/ethernet/intel/i40e/i40e.h
@@ -836,16 +836,23 @@ struct i40e_vsi {
 	struct i40e_eth_stats eth_stats;
 	struct i40e_eth_stats eth_stats_offsets;
 	u64 tx_restart;
+	u64 tx_restart_base;
 	u64 tx_busy;
+	u64 tx_busy_base;
 	u64 tx_linearize;
 	u64 tx_force_wb;
 	u64 tx_stopped;
+	u64 tx_stopped_base;
+	u64 tx_bytes;
+	u64 tx_packets;
 	u64 rx_buf_failed;
 	u64 rx_page_failed;
 	u64 rx_page_reuse;
 	u64 rx_page_alloc;
 	u64 rx_page_waive;
 	u64 rx_page_busy;
+	u64 rx_bytes;
+	u64 rx_packets;
 
 	/* These are containers of ring pointers, allocated at run-time */
 	struct i40e_ring **rx_rings;
diff --git a/drivers/net/ethernet/intel/i40e/i40e_main.c b/drivers/net/ethernet/intel/i40e/i40e_main.c
index 926d001b2150..0d0b3619ec56 100644
--- a/drivers/net/ethernet/intel/i40e/i40e_main.c
+++ b/drivers/net/ethernet/intel/i40e/i40e_main.c
@@ -7,6 +7,7 @@
 #include <linux/if_bridge.h>
 #include <linux/if_macvlan.h>
 #include <linux/module.h>
+#include <net/netdev_queues.h>
 #include <net/pkt_cls.h>
 #include <net/xdp_sock_drv.h>
 
@@ -11686,6 +11687,26 @@ static int i40e_vsi_clear(struct i40e_vsi *vsi)
 	return 0;
 }
 
+static void i40e_vsi_aggregate_tx_counters(struct i40e_vsi *vsi,
+					   struct i40e_ring *tx_ring)
+{
+	if (!tx_ring)
+		return;
+
+	vsi->tx_bytes += tx_ring->stats.bytes;
+	vsi->tx_packets += tx_ring->stats.packets;
+}
+
+static void i40e_vsi_aggregate_rx_counters(struct i40e_vsi *vsi,
+					   struct i40e_ring *rx_ring)
+{
+	if (!rx_ring)
+		return;
+
+	vsi->rx_bytes += rx_ring->stats.bytes;
+	vsi->rx_packets += rx_ring->stats.packets;
+}
+
 /**
  * i40e_vsi_clear_rings - Deallocates the Rx and Tx rings for the provided VSI
  * @vsi: the VSI being cleaned
@@ -11696,6 +11717,13 @@ static void i40e_vsi_clear_rings(struct i40e_vsi *vsi)
 
 	if (vsi->tx_rings && vsi->tx_rings[0]) {
 		for (i = 0; i < vsi->alloc_queue_pairs; i++) {
+			struct i40e_ring *xdp_ring = vsi->xdp_rings ?
+						     vsi->xdp_rings[i] : NULL;
+
+			i40e_vsi_aggregate_tx_counters(vsi, vsi->tx_rings[i]);
+			i40e_vsi_aggregate_tx_counters(vsi, xdp_ring);
+			i40e_vsi_aggregate_rx_counters(vsi, vsi->rx_rings[i]);
+
 			kfree_rcu(vsi->tx_rings[i], rcu);
 			WRITE_ONCE(vsi->tx_rings[i], NULL);
 			WRITE_ONCE(vsi->rx_rings[i], NULL);
@@ -13625,6 +13653,110 @@ static const struct net_device_ops i40e_netdev_ops = {
 	.ndo_hwtstamp_set	= i40e_ptp_hwtstamp_set,
 };
 
+static void i40e_get_queue_stats_rx(struct net_device *dev, int idx,
+				    struct netdev_queue_stats_rx *rx)
+{
+	struct i40e_netdev_priv *np = netdev_priv(dev);
+	struct i40e_vsi *vsi = np->vsi;
+	struct i40e_ring *rx_ring;
+	unsigned int start;
+	u64 bytes, packets;
+
+	rcu_read_lock();
+	rx_ring = READ_ONCE(vsi->rx_rings[idx]);
+	if (!rx_ring)
+		goto unlock;
+
+	do {
+		start = u64_stats_fetch_begin(&rx_ring->syncp);
+		bytes = rx_ring->stats.bytes;
+		packets = rx_ring->stats.packets;
+	} while (u64_stats_fetch_retry(&rx_ring->syncp, start));
+
+	rx->bytes = bytes;
+	rx->packets = packets;
+
+unlock:
+	rcu_read_unlock();
+}
+
+static void i40e_zero_tx_ring_stats(struct netdev_queue_stats_tx *tx)
+{
+	tx->bytes = 0;
+	tx->packets = 0;
+	tx->stop = 0;
+	tx->wake = 0;
+	tx->hw_drops = 0;
+}
+
+static void i40e_add_tx_ring_stats(struct i40e_ring *tx_ring,
+				   struct netdev_queue_stats_tx *tx)
+{
+	u64 bytes, packets;
+	unsigned int start;
+
+	do {
+		start = u64_stats_fetch_begin(&tx_ring->syncp);
+		bytes = tx_ring->stats.bytes;
+		packets = tx_ring->stats.packets;
+	} while (u64_stats_fetch_retry(&tx_ring->syncp, start));
+
+	tx->bytes += bytes;
+	tx->packets += packets;
+
+	tx->stop += tx_ring->tx_stats.tx_stopped;
+	tx->wake += tx_ring->tx_stats.restart_queue;
+	tx->hw_drops += tx_ring->tx_stats.tx_busy;
+}
+
+static void i40e_get_queue_stats_tx(struct net_device *dev, int idx,
+				    struct netdev_queue_stats_tx *tx)
+{
+	struct i40e_netdev_priv *np = netdev_priv(dev);
+	struct i40e_vsi *vsi = np->vsi;
+	struct i40e_ring *tx_ring;
+
+	rcu_read_lock();
+	tx_ring = READ_ONCE(vsi->tx_rings[idx]);
+	if (!tx_ring)
+		goto out;
+
+	i40e_zero_tx_ring_stats(tx);
+	i40e_add_tx_ring_stats(tx_ring, tx);
+
+	if (i40e_enabled_xdp_vsi(vsi)) {
+		tx_ring = READ_ONCE(vsi->xdp_rings[idx]);
+		if (tx_ring)
+			i40e_add_tx_ring_stats(tx_ring, tx);
+	}
+
+out:
+	rcu_read_unlock();
+}
+
+static void i40e_get_base_stats(struct net_device *dev,
+				struct netdev_queue_stats_rx *rx,
+				struct netdev_queue_stats_tx *tx)
+{
+	struct i40e_netdev_priv *np = netdev_priv(dev);
+	struct i40e_vsi *vsi = np->vsi;
+
+	tx->bytes = vsi->tx_bytes;
+	tx->packets = vsi->tx_packets;
+	tx->wake = vsi->tx_restart_base;
+	tx->stop = vsi->tx_stopped_base;
+	tx->hw_drops = vsi->tx_busy_base;
+
+	rx->bytes = vsi->rx_bytes;
+	rx->packets = vsi->rx_packets;
+}
+
+static const struct netdev_stat_ops i40e_stat_ops = {
+	.get_queue_stats_rx	= i40e_get_queue_stats_rx,
+	.get_queue_stats_tx	= i40e_get_queue_stats_tx,
+	.get_base_stats		= i40e_get_base_stats,
+};
+
 /**
  * i40e_config_netdev - Setup the netdev flags
  * @vsi: the VSI being configured
@@ -13787,6 +13919,7 @@ static int i40e_config_netdev(struct i40e_vsi *vsi)
 	/* Setup netdev TC information */
 	i40e_vsi_config_netdev_tc(vsi, vsi->tc_config.enabled_tc);
 
+	netdev->stat_ops = &i40e_stat_ops;
 	netdev->netdev_ops = &i40e_netdev_ops;
 	netdev->watchdog_timeo = 5 * HZ;
 	i40e_set_ethtool_ops(netdev);
-- 
2.53.0


^ permalink raw reply related

* [PATCH iwl-next 0/2] i40e: implement per-queue stats
From: Paolo Abeni @ 2026-04-08 11:43 UTC (permalink / raw)
  To: intel-wired-lan
  Cc: Tony Nguyen, Przemek Kitszel, Andrew Lunn, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Alexei Starovoitov, Daniel Borkmann,
	Jesper Dangaard Brouer, John Fastabend, Stanislav Fomichev,
	netdev

The i40e driver already collects some per queue statistics, but does
not expose them to the user-space using the standard interface.

Implement the stat_ops callbacks and extends the already collected info
with basic GSO counters. Overall this allows passing the kernel NIC
drivers TSO test cases.

Paolo Abeni (2):
  i40e: implement basic per-queue stats
  i40e: keep track of per queue gso counters.

 drivers/net/ethernet/intel/i40e/i40e.h        |   9 ++
 drivers/net/ethernet/intel/i40e/i40e_main.c   | 144 ++++++++++++++++++
 drivers/net/ethernet/intel/i40e/i40e_txrx.c   |   8 +-
 drivers/net/ethernet/intel/i40e/i40e_txrx.h   |   2 +
 .../ethernet/intel/i40e/i40e_txrx_common.h    |   6 +-
 drivers/net/ethernet/intel/i40e/i40e_xsk.c    |   2 +-
 6 files changed, 168 insertions(+), 3 deletions(-)

-- 
2.53.0


^ permalink raw reply

* Re: [PATCH v2] rxrpc/proc: size address buffers for %pISpc output
From: David Howells @ 2026-04-08 11:40 UTC (permalink / raw)
  To: Pengpeng Hou
  Cc: dhowells, Marc Dionne, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, linux-afs, netdev, linux-kernel
In-Reply-To: <20260406170001.2-rxrpc-proc-v2-pengpeng@iscas.ac.cn>

I'm going to add:

    Fixes: 75b54cb57ca3 ("rxrpc: Add IPv6 support")

and change the subject to "rxrpc: proc:" rather than "rxrpc/proc:"

David


^ permalink raw reply

* Re: [PATCH net-next v3 0/4] net: move .getsockopt away from __user buffers
From: David Laight @ 2026-04-08 11:26 UTC (permalink / raw)
  To: Breno Leitao
  Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, Kuniyuki Iwashima, Willem de Bruijn, metze, axboe,
	Stanislav Fomichev, io-uring, bpf, netdev, Linus Torvalds,
	linux-kernel, kernel-team
In-Reply-To: <20260408-getsockopt-v3-0-061bb9cb355d@debian.org>

On Wed, 08 Apr 2026 03:30:28 -0700
Breno Leitao <leitao@debian.org> wrote:

> Currently, the .getsockopt callback requires __user pointers:
> 
>   int (*getsockopt)(struct socket *sock, int level,
>                     int optname, char __user *optval, int __user *optlen);
> 
> This prevents kernel callers (io_uring, BPF) from using getsockopt on
> levels other than SOL_SOCKET, since they pass kernel pointers.
> 
> Following Linus' suggestion [0], this series introduces sockopt_t, a
> type-safe wrapper around iov_iter, and a getsockopt_iter callback that
> works with both user and kernel buffers. AF_PACKET and CAN raw are
> converted as initial users, with selftests covering the trickiest
> conversion patterns.

What are you doing about the cases where 'optlen' is a complete lie?
IIRC there is one related to some form of async io where it is just
the length of the header, the actual buffer length depends on
data in the header.
This doesn't matter with the existing code for applications, when they
get it wrong they just crash.
But kernel users will need to pass the actual buffer length separately
from optlen.
It also affects any code that tries to cache the actual data and copy
it back to userspace in the syscall wrapper - which makes sense for
most short getsockopt.

(This is different from historic code where the length might be
assumed to be 4 regardless of what was passed.)

	David

^ permalink raw reply

* [net-next v1 v1 4/5] net: stmmac: starfive: Add JHB100 SGMII interface
From: Minda Chen @ 2026-04-08  8:44 UTC (permalink / raw)
  To: Alexandre Torgue, Andrew Lunn, David S . Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Maxime Coquelin,
	Emil Renner Berthing, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, netdev
  Cc: linux-kernel, linux-stm32, devicetree, Minda Chen
In-Reply-To: <20260408084416.29753-1-minda.chen@starfivetech.com>

Add JHB100 compatible and SGMII support. JHB100 soc contains
2 SGMII interfaces and integrated with serdes PHY. SGMII with
split TX/RX MAC clock and need to set 2.5M/25M/125M TX/RX clock
rate in 10M/100M/1000M speed mode.

Signed-off-by: Minda Chen <minda.chen@starfivetech.com>
---
 .../ethernet/stmicro/stmmac/dwmac-starfive.c  | 36 +++++++++++++++++--
 1 file changed, 34 insertions(+), 2 deletions(-)

diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-starfive.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-starfive.c
index 16b955a6d77b..df7b2fc9989d 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac-starfive.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-starfive.c
@@ -26,6 +26,7 @@ struct starfive_dwmac_data {
 struct starfive_dwmac {
 	struct device *dev;
 	const struct starfive_dwmac_data *data;
+	struct clk *sgmii_rx;
 };
 
 static int starfive_dwmac_set_mode(struct plat_stmmacenet_data *plat_dat)
@@ -36,6 +37,9 @@ static int starfive_dwmac_set_mode(struct plat_stmmacenet_data *plat_dat)
 	int phy_intf_sel;
 	int err;
 
+	if (plat_dat->phy_interface == PHY_INTERFACE_MODE_SGMII)
+		return 0;
+
 	phy_intf_sel = stmmac_get_phy_intf_sel(plat_dat->phy_interface);
 	if (phy_intf_sel != PHY_INTF_SEL_RGMII &&
 	    phy_intf_sel != PHY_INTF_SEL_RMII) {
@@ -68,6 +72,24 @@ static int starfive_dwmac_set_mode(struct plat_stmmacenet_data *plat_dat)
 	return 0;
 }
 
+static int stmmac_starfive_sgmii_set_clk_rate(void *bsp_priv, struct clk *clk_tx_i,
+					      phy_interface_t interface, int speed)
+{
+	struct starfive_dwmac *dwmac = (void *)bsp_priv;
+	long rate = rgmii_clock(speed);
+	int ret;
+
+	/* MAC clock rate the same as RGMII */
+	if (rate < 0)
+		return 0;
+
+	ret = clk_set_rate(clk_tx_i, rate);
+	if (ret)
+		return ret;
+
+	return clk_set_rate(dwmac->sgmii_rx, rate);
+}
+
 static int starfive_dwmac_probe(struct platform_device *pdev)
 {
 	struct plat_stmmacenet_data *plat_dat;
@@ -102,14 +124,23 @@ static int starfive_dwmac_probe(struct platform_device *pdev)
 		return dev_err_probe(&pdev->dev, PTR_ERR(clk_gtx),
 				     "error getting gtx clock\n");
 
+	dwmac->sgmii_rx = devm_clk_get_optional(&pdev->dev, "rx");
+	if (IS_ERR(dwmac->sgmii_rx))
+		return dev_err_probe(&pdev->dev, PTR_ERR(dwmac->sgmii_rx),
+				     "error getting sgmii rx clock\n");
+
 	/* Generally, the rgmii_tx clock is provided by the internal clock,
 	 * which needs to match the corresponding clock frequency according
 	 * to different speeds. If the rgmii_tx clock is provided by the
 	 * external rgmii_rxin, there is no need to configure the clock
 	 * internally, because rgmii_rxin will be adaptively adjusted.
 	 */
-	if (!device_property_read_bool(&pdev->dev, "starfive,tx-use-rgmii-clk"))
-		plat_dat->set_clk_tx_rate = stmmac_set_clk_tx_rate;
+	if (!device_property_read_bool(&pdev->dev, "starfive,tx-use-rgmii-clk")) {
+		if (plat_dat->phy_interface == PHY_INTERFACE_MODE_SGMII)
+			plat_dat->set_clk_tx_rate = stmmac_starfive_sgmii_set_clk_rate;
+		else
+			plat_dat->set_clk_tx_rate = stmmac_set_clk_tx_rate;
+	}
 
 	dwmac->dev = &pdev->dev;
 	plat_dat->flags |= STMMAC_FLAG_EN_TX_LPI_CLK_PHY_CAP;
@@ -130,6 +161,7 @@ static const struct starfive_dwmac_data jh7100_data = {
 static const struct of_device_id starfive_dwmac_match[] = {
 	{ .compatible = "starfive,jh7100-dwmac", .data = &jh7100_data },
 	{ .compatible = "starfive,jh7110-dwmac" },
+	{ .compatible = "starfive,jhb100-dwmac" },
 	{ /* sentinel */ }
 };
 MODULE_DEVICE_TABLE(of, starfive_dwmac_match);
-- 
2.17.1


^ permalink raw reply related

* RE: [PATCH v4 net-next 13/14] net: dsa: netc: initialize buffer bool table and implement flow-control
From: Wei Fang @ 2026-04-08 11:16 UTC (permalink / raw)
  To: Jakub Kicinski
  Cc: Claudiu Manoil, Vladimir Oltean, Clark Wang,
	andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com,
	pabeni@redhat.com, robh@kernel.org, krzk+dt@kernel.org,
	conor+dt@kernel.org, f.fainelli@gmail.com, Frank Li,
	chleroy@kernel.org, horms@kernel.org, linux@armlinux.org.uk,
	andrew@lunn.ch, netdev@vger.kernel.org,
	linux-kernel@vger.kernel.org, devicetree@vger.kernel.org,
	linuxppc-dev@lists.ozlabs.org,
	linux-arm-kernel@lists.infradead.org, imx@lists.linux.dev
In-Reply-To: <20260403011745.1795759-1-kuba@kernel.org>

> > +static u32 netc_get_buffer_pool_num(struct netc_switch *priv)
> > +{
> > +	/* The BPCAPR register is a read only register, the hardware
> > +	 * guarantees that the BPCAPR_NUM_BP field will not be 0.
> > +	 */
> > +	return netc_base_rd(&priv->regs, NETC_BPCAPR) & BPCAPR_NUM_BP;
> > +}
> [ ... ]
> > +static void netc_ipv_to_buffer_pool_mapping(struct netc_switch *priv)
> > +{
> > +	int bp_per_port = priv->num_bp / priv->info->num_ports;
> > +	int q = NETC_IPV_NUM / bp_per_port;
> > +	int r = NETC_IPV_NUM % bp_per_port;
> 
> If the hardware register read in netc_get_buffer_pool_num() returns a value
> smaller than priv->info->num_ports (for instance, during a bus error or
> reset state), bp_per_port will evaluate to 0.
> 
> Can this lead to a division by zero panic here?
> 
> Should priv->num_bp be validated against the number of ports before
> performing this calculation to ensure the driver fails gracefully if the
> hardware returns an unexpected value?

If the hardware is so unstable and prone to such malfunctions, that would
be terrible, but I believe it won't. The AI ​​review is being a bit too nitpicky.
Anyway, I will ll add this check to avoid AI review warnings.


^ permalink raw reply

* [PATCH net 1/2] batman-adv: reject oversized global TT response buffers
From: Simon Wunderlich @ 2026-04-08 11:02 UTC (permalink / raw)
  To: davem, kuba
  Cc: netdev, b.a.t.m.a.n, Ruide Cao, stable, Yifan Wu, Juefei Pu,
	Yuan Tan, Xin Liu, Ren Wei, Ren Wei, Sven Eckelmann,
	Simon Wunderlich
In-Reply-To: <20260408110255.976389-1-sw@simonwunderlich.de>

From: Ruide Cao <caoruide123@gmail.com>

batadv_tt_prepare_tvlv_global_data() builds the allocation length for a
global TT response in 16-bit temporaries. When a remote originator
advertises a large enough global TT, the TT payload length plus the VLAN
header offset can exceed 65535 and wrap before kmalloc().

The full-table response path still uses the original TT payload length when
it fills tt_change, so the wrapped allocation is too small and
batadv_tt_prepare_tvlv_global_data() writes past the end of the heap object
before the later packet-size check runs.

Fix this by rejecting TT responses whose TVLV value length cannot fit in
the 16-bit TVLV payload length field.

Fixes: 7ea7b4a14275 ("batman-adv: make the TT CRC logic VLAN specific")
Cc: stable@vger.kernel.org
Reported-by: Yifan Wu <yifanwucs@gmail.com>
Reported-by: Juefei Pu <tomapufckgml@gmail.com>
Co-developed-by: Yuan Tan <yuantan098@gmail.com>
Signed-off-by: Yuan Tan <yuantan098@gmail.com>
Suggested-by: Xin Liu <bird@lzu.edu.cn>
Tested-by: Ren Wei <enjou1224z@gmail.com>
Signed-off-by: Ruide Cao <caoruide123@gmail.com>
Signed-off-by: Ren Wei <n05ec@lzu.edu.cn>
Signed-off-by: Sven Eckelmann <sven@narfation.org>
Signed-off-by: Simon Wunderlich <sw@simonwunderlich.de>
---
 net/batman-adv/translation-table.c | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c
index 6e95e883c2bf0..05cddcf994f65 100644
--- a/net/batman-adv/translation-table.c
+++ b/net/batman-adv/translation-table.c
@@ -798,8 +798,8 @@ batadv_tt_prepare_tvlv_global_data(struct batadv_orig_node *orig_node,
 {
 	u16 num_vlan = 0;
 	u16 num_entries = 0;
-	u16 change_offset;
-	u16 tvlv_len;
+	u16 tvlv_len = 0;
+	unsigned int change_offset;
 	struct batadv_tvlv_tt_vlan_data *tt_vlan;
 	struct batadv_orig_node_vlan *vlan;
 	u8 *tt_change_ptr;
@@ -816,6 +816,11 @@ batadv_tt_prepare_tvlv_global_data(struct batadv_orig_node *orig_node,
 	if (*tt_len < 0)
 		*tt_len = batadv_tt_len(num_entries);
 
+	if (change_offset > U16_MAX || *tt_len > U16_MAX - change_offset) {
+		*tt_len = 0;
+		goto out;
+	}
+
 	tvlv_len = *tt_len;
 	tvlv_len += change_offset;
 
-- 
2.47.3


^ permalink raw reply related

* [PATCH net 0/2] pull request: batman-adv 2025-04-08
From: Simon Wunderlich @ 2026-04-08 11:02 UTC (permalink / raw)
  To: davem, kuba; +Cc: netdev, b.a.t.m.a.n, Simon Wunderlich

Hi David, hi Jakub,

here are two bugfixes for batman-adv which we would like to have integrated into net.

Please pull or let me know of any problem!

Thank you,
      Simon

The following changes since commit 0d4aef630be9d5f9c1227d07669c26c4383b5ad0:

  batman-adv: avoid OGM aggregation when skb tailroom is insufficient (2026-03-14 08:29:47 +0100)

are available in the Git repository at:

  https://git.open-mesh.org/linux-merge.git tags/batadv-net-pullrequest-20260408

for you to fetch changes up to 82d8701b2c930d0e96b0dbc9115a218d791cb0d2:

  batman-adv: hold claim backbone gateways by reference (2026-04-06 15:42:29 +0200)

----------------------------------------------------------------
Here are two batman-adv bugfixes:

 - reject oversized global TT response buffers, by Ruide Cao

 - hold claim backbone gateways by reference, by Haoze Xie

----------------------------------------------------------------
Haoze Xie (1):
      batman-adv: hold claim backbone gateways by reference

Ruide Cao (1):
      batman-adv: reject oversized global TT response buffers

 net/batman-adv/bridge_loop_avoidance.c | 27 ++++++++++++++++++---------
 net/batman-adv/translation-table.c     |  9 +++++++--
 2 files changed, 25 insertions(+), 11 deletions(-)

^ permalink raw reply

* RE: [PATCH v4 net-next 12/14] net: dsa: netc: add more basic functions support
From: Wei Fang @ 2026-04-08 11:09 UTC (permalink / raw)
  To: Jakub Kicinski
  Cc: Claudiu Manoil, Vladimir Oltean, Clark Wang,
	andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com,
	pabeni@redhat.com, robh@kernel.org, krzk+dt@kernel.org,
	conor+dt@kernel.org, f.fainelli@gmail.com, Frank Li,
	chleroy@kernel.org, horms@kernel.org, linux@armlinux.org.uk,
	andrew@lunn.ch, netdev@vger.kernel.org,
	linux-kernel@vger.kernel.org, devicetree@vger.kernel.org,
	linuxppc-dev@lists.ozlabs.org,
	linux-arm-kernel@lists.infradead.org, imx@lists.linux.dev
In-Reply-To: <20260403011743.1795726-1-kuba@kernel.org>

> > +static int netc_add_standalone_vlan_entry(struct netc_switch *priv)
> > +{
> > +	u32 bitmap_stg = VFT_STG_ID(0) | netc_available_port_bitmap(priv);
> 
> Does putting all standalone ports into a single shared VLAN 0 break the DSA
> isolation requirements?
> 
> If a user manually adds a static FDB entry to a standalone port, it is
> programmed into VLAN 0. Because all standalone ports reside in this VLAN, a
> frame arriving on one standalone port with a matching destination MAC might
> be hardware-switched directly to another standalone port, bypassing the CPU.

I think the FDB entry can only be added to the CPU port if the port is a
standalone port not a bridge port. So this case will not be true.

> 
> [ ... ]
> 
> > +static int netc_port_set_fdb_entry(struct netc_port *np,
> > +				   const unsigned char *addr, u16 vid)
> > +{
> 
> [ ... ]
> 
> > +	/* If the entry already exists, but not on this port, we need to
> > +	 * update the port bitmap. In general, it should only be valid
> > +	 * for multicast or broadcast address.
> > +	 */
> > +	port_bitmap ^= BIT(port);
> > +	entry->cfge.port_bitmap = cpu_to_le32(port_bitmap);
> 
> Can this XOR operation cause multi-port floods during unicast MAC migrations?
> 
> When a unicast MAC address moves to a new port, the bridging layer issues an
> ADD for the new port. The XOR operation leaves the old port bit set alongside
> the new port bit. Does the driver need to differentiate between unicast and
> multicast addresses and explicitly overwrite the bitmap for unicast entries?

I think the old FDB entry should be removed first when a unicast MAC address
moves to a new port rather than only adding a new entry. If AI refers to adding
before deleting, then this problem will indeed occur, and it is unavoidable.

> 
> [ ... ]
> 
> > +static int netc_port_del_fdb_entry(struct netc_port *np,
> > +				   const unsigned char *addr, u16 vid)
> > +{
> > +	struct netc_switch *priv = np->switch_priv;
> > +	struct ntmp_user *ntmp = &priv->ntmp;
> > +	struct netc_fdb_entry *entry;
> > +	int port = np->dp->index;
> > +	u32 port_bitmap;
> > +	int err = 0;
> > +
> > +	mutex_lock(&priv->fdbt_lock);
> > +
> > +	entry = netc_lookup_fdb_entry(priv, addr, vid);
> > +	if (unlikely(!entry))
> > +		goto unlock_fdbt;
> 
> Will this prevent dynamically learned FDB entries from being deleted?
> 
> Since the CPU port is configured with MLO_HW, the switch hardware

Currently we only support single port mode, so only support static FDB
entry. The MLO_HW should be removed from the code, I will improve
the logic.

> dynamically
> learns MAC addresses, but they are not added to the software priv->fdb_list.
> If a user attempts to delete a dynamic entry, the software lookup will fail
> here and return 0 (success), leaving the stale dynamically learned entry
> active in hardware.
> 
> [ ... ]
> 
> > +static int netc_port_change_mtu(struct dsa_switch *ds,
> > +				int port, int mtu)
> > +{
> > +	u32 max_frame_size = mtu + VLAN_ETH_HLEN + ETH_FCS_LEN;
> > +	struct netc_port *np = NETC_PORT(ds, port);
> > +
> > +	if (dsa_is_cpu_port(ds, port))
> > +		max_frame_size += NETC_TAG_MAX_LEN;
> 
> Does this double-count the DSA tag overhead for the CPU port?
> 
> The DSA core computes the CPU port's MTU by adding the tagger overhead
> before
> calling the switch's .port_change_mtu() function. If the maximum MTU of 9582
> bytes is requested, the CPU port receives an MTU of 9596. Adding
> NETC_TAG_MAX_LEN again here results in 9628 bytes, which exceeds the
> hardware limit of 9600 (NETC_MAX_FRAME_LEN) and might lead to truncation.

It seems a bug, I will fix it.

> 
> [ ... ]
> 
> > +static int netc_port_fdb_add(struct dsa_switch *ds, int port,
> > +			     const unsigned char *addr, u16 vid,
> > +			     struct dsa_db db)
> > +{
> > +	struct netc_port *np = NETC_PORT(ds, port);
> > +
> > +	/* Currently, we only support standalone port mode, so all VLANs
> > +	 * should be converted to NETC_STANDALONE_PVID.
> > +	 */
> > +	return netc_port_set_fdb_entry(np, addr, NETC_STANDALONE_PVID);
> > +}
> 
> Is it safe to silently override user-requested VLAN IDs to VLAN 0?
> 
> If a user explicitly attempts to configure a MAC address for a specific VLAN,
> the hardware is programmed for VLAN 0 and returns success. Should this
> instead check if the requested vid is not 0 and not NETC_STANDALONE_PVID,
> and return -EOPNOTSUPP to inform the networking stack?

Okay, I will add a check.

> 
> [ ... ]
> 
> > +static void netc_port_set_host_flood(struct dsa_switch *ds, int port,
> > +				     bool uc, bool mc)
> > +{
> > +	struct netc_port *np = NETC_PORT(ds, port);
> > +
> > +	if (np->uc == uc && np->mc == mc)
> > +		return;
> > +
> > +	/* IPFT does not support in-place updates to the KEYE element,
> > +	 * so we need to delete the old IPFT entry and then add a new
> > +	 * one.
> > +	 */
> > +	if (np->host_flood)
> > +		netc_port_remove_host_flood(np);
> > +
> > +	if (netc_port_add_host_flood_rule(np, uc, mc))
> > +		dev_err(ds->dev, "Failed to add host flood rule on port %d\n",
> > +			port);
> > +}
> 
> Could this permanently lose the host flood configuration if the new rule
> addition fails?
> 
> If the call to netc_port_add_host_flood_rule() fails (for example, due to
> -ENOMEM), the function logs an error and returns, leaving the port entirely
> without a host flood rule. Should the driver revert to the previous
> configuration on failure to avoid blackholing traffic meant for the CPU?

Okay, I will improve it.


^ permalink raw reply

* [PATCH iwl-net v2] ice: fix SMA and U.FL pin state changes affecting paired pin
From: Petr Oros @ 2026-04-08 11:05 UTC (permalink / raw)
  To: netdev
  Cc: Petr Oros, Tony Nguyen, Przemek Kitszel, Andrew Lunn,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Arkadiusz Kubalewski, Simon Horman, intel-wired-lan, linux-kernel

SMA and U.FL pins share physical signal paths in pairs (SMA1/U.FL1 and
SMA2/U.FL2) controlled by the PCA9575 GPIO expander.  Each pair can
only have one active pin at a time: SMA1 output and U.FL1 output share
the same CGU output, SMA2 input and U.FL2 input share the same CGU
input.  The PCA9575 register bits determine which connector in each
pair owns the signal path.

The driver does not account for this pairing in two places:

ice_dpll_ufl_pin_state_set() modifies PCA9575 bits and disables the
backing CGU pin without checking whether the U.FL pin is currently
active.  Disconnecting an already inactive U.FL pin flips bits that
the paired SMA pin relies on, breaking its connection.

ice_dpll_sma_direction_set() does not propagate direction changes to
the paired U.FL pin.  For SMA2/U.FL2 the ICE_SMA2_UFL2_RX_DIS bit is
never managed, so U.FL2 stays disconnected after SMA2 switches to
output.  For both pairs the backing CGU pin of the U.FL side is never
enabled when a direction change activates it, so userspace sees the
pin as disconnected even though the routing is correct.

Fix by guarding the U.FL disconnect path against inactive pins and by
updating the paired U.FL pin fully on SMA direction changes: manage
ICE_SMA2_UFL2_RX_DIS for the SMA2/U.FL2 pair and enable the backing
CGU pin whenever the peer becomes active.

Fixes: 2dd5d03c77e2 ("ice: redesign dpll sma/u.fl pins control")
Signed-off-by: Petr Oros <poros@redhat.com>
---
v2:
 - fix ice_dpll_sma_direction_set() to manage ICE_SMA2_UFL2_RX_DIS
   when SMA2 direction changes
 - enable paired U.FL backing CGU pin when direction change makes
   it active, so it reports as connected immediately
 - (both reported by Intel test on the SMA init and notification
   patch threads)
v1: https://lore.kernel.org/all/20260325151050.2081977-1-poros@redhat.com/
---
 drivers/net/ethernet/intel/ice/ice_dpll.c | 50 ++++++++++++++++++++++-
 1 file changed, 49 insertions(+), 1 deletion(-)

diff --git a/drivers/net/ethernet/intel/ice/ice_dpll.c b/drivers/net/ethernet/intel/ice/ice_dpll.c
index 498ec2c045f384..3f8cd5b8298b57 100644
--- a/drivers/net/ethernet/intel/ice/ice_dpll.c
+++ b/drivers/net/ethernet/intel/ice/ice_dpll.c
@@ -1171,6 +1171,8 @@ static int ice_dpll_sma_direction_set(struct ice_dpll_pin *p,
 				      enum dpll_pin_direction direction,
 				      struct netlink_ext_ack *extack)
 {
+	struct ice_dplls *d = &p->pf->dplls;
+	struct ice_dpll_pin *peer;
 	u8 data;
 	int ret;
 
@@ -1189,8 +1191,9 @@ static int ice_dpll_sma_direction_set(struct ice_dpll_pin *p,
 	case ICE_DPLL_PIN_SW_2_IDX:
 		if (direction == DPLL_PIN_DIRECTION_INPUT) {
 			data &= ~ICE_SMA2_DIR_EN;
+			data |= ICE_SMA2_UFL2_RX_DIS;
 		} else {
-			data &= ~ICE_SMA2_TX_EN;
+			data &= ~(ICE_SMA2_TX_EN | ICE_SMA2_UFL2_RX_DIS);
 			data |= ICE_SMA2_DIR_EN;
 		}
 		break;
@@ -1202,6 +1205,34 @@ static int ice_dpll_sma_direction_set(struct ice_dpll_pin *p,
 		ret = ice_dpll_pin_state_update(p->pf, p,
 						ICE_DPLL_PIN_TYPE_SOFTWARE,
 						extack);
+	if (ret)
+		return ret;
+
+	/* When a direction change activates the paired U.FL pin, enable
+	 * its backing CGU pin so the pin reports as connected. Without
+	 * this the U.FL routing is correct but the CGU pin stays disabled
+	 * and userspace sees the pin as disconnected.  Do not disable the
+	 * backing pin when U.FL becomes inactive because the SMA pin may
+	 * still be using it.
+	 */
+	peer = &d->ufl[p->idx];
+	if (peer->active) {
+		struct ice_dpll_pin *target;
+		enum ice_dpll_pin_type type;
+
+		if (peer->output) {
+			target = peer->output;
+			type = ICE_DPLL_PIN_TYPE_OUTPUT;
+		} else {
+			target = peer->input;
+			type = ICE_DPLL_PIN_TYPE_INPUT;
+		}
+		ret = ice_dpll_pin_enable(&p->pf->hw, target,
+					  d->eec.dpll_idx, type, extack);
+		if (!ret)
+			ret = ice_dpll_pin_state_update(p->pf, target,
+							type, extack);
+	}
 
 	return ret;
 }
@@ -1253,6 +1284,14 @@ ice_dpll_ufl_pin_state_set(const struct dpll_pin *pin, void *pin_priv,
 			data &= ~ICE_SMA1_MASK;
 			enable = true;
 		} else if (state == DPLL_PIN_STATE_DISCONNECTED) {
+			/* Skip if U.FL1 is not active, setting TX_EN
+			 * while DIR_EN is set would also deactivate
+			 * the paired SMA1 output.
+			 */
+			if (data & (ICE_SMA1_DIR_EN | ICE_SMA1_TX_EN)) {
+				ret = 0;
+				goto unlock;
+			}
 			data |= ICE_SMA1_TX_EN;
 			enable = false;
 		} else {
@@ -1267,6 +1306,15 @@ ice_dpll_ufl_pin_state_set(const struct dpll_pin *pin, void *pin_priv,
 			data &= ~ICE_SMA2_UFL2_RX_DIS;
 			enable = true;
 		} else if (state == DPLL_PIN_STATE_DISCONNECTED) {
+			/* Skip if U.FL2 is not active, setting
+			 * UFL2_RX_DIS could also disable the paired
+			 * SMA2 input.
+			 */
+			if (!(data & ICE_SMA2_DIR_EN) ||
+			    (data & ICE_SMA2_UFL2_RX_DIS)) {
+				ret = 0;
+				goto unlock;
+			}
 			data |= ICE_SMA2_UFL2_RX_DIS;
 			enable = false;
 		} else {
-- 
2.52.0


^ permalink raw reply related

* [PATCH net 2/2] batman-adv: hold claim backbone gateways by reference
From: Simon Wunderlich @ 2026-04-08 11:02 UTC (permalink / raw)
  To: davem, kuba
  Cc: netdev, b.a.t.m.a.n, Haoze Xie, stable, Yifan Wu, Juefei Pu,
	Yuan Tan, Xin Liu, Ao Zhou, Sven Eckelmann, Simon Wunderlich
In-Reply-To: <20260408110255.976389-1-sw@simonwunderlich.de>

From: Haoze Xie <royenheart@gmail.com>

batadv_bla_add_claim() can replace claim->backbone_gw and drop the old
gateway's last reference while readers still follow the pointer.

The netlink claim dump path dereferences claim->backbone_gw->orig and
takes claim->backbone_gw->crc_lock without pinning the underlying
backbone gateway. batadv_bla_check_claim() still has the same naked
pointer access pattern.

Reuse batadv_bla_claim_get_backbone_gw() in both readers so they operate
on a stable gateway reference until the read-side work is complete.
This keeps the dump and claim-check paths aligned with the lifetime
rules introduced for the other BLA claim readers.

Fixes: 23721387c409 ("batman-adv: add basic bridge loop avoidance code")
Fixes: 04f3f5bf1883 ("batman-adv: add B.A.T.M.A.N. Dump BLA claims via netlink")
Cc: stable@vger.kernel.org
Reported-by: Yifan Wu <yifanwucs@gmail.com>
Reported-by: Juefei Pu <tomapufckgml@gmail.com>
Co-developed-by: Yuan Tan <yuantan098@gmail.com>
Signed-off-by: Yuan Tan <yuantan098@gmail.com>
Suggested-by: Xin Liu <bird@lzu.edu.cn>
Signed-off-by: Haoze Xie <royenheart@gmail.com>
Signed-off-by: Ao Zhou <n05ec@lzu.edu.cn>
Signed-off-by: Sven Eckelmann <sven@narfation.org>
Signed-off-by: Simon Wunderlich <sw@simonwunderlich.de>
---
 net/batman-adv/bridge_loop_avoidance.c | 27 +++++++++++++++++---------
 1 file changed, 18 insertions(+), 9 deletions(-)

diff --git a/net/batman-adv/bridge_loop_avoidance.c b/net/batman-adv/bridge_loop_avoidance.c
index 3dc791c15bf72..648fa97ea913f 100644
--- a/net/batman-adv/bridge_loop_avoidance.c
+++ b/net/batman-adv/bridge_loop_avoidance.c
@@ -2130,6 +2130,7 @@ batadv_bla_claim_dump_entry(struct sk_buff *msg, u32 portid,
 			    struct batadv_bla_claim *claim)
 {
 	const u8 *primary_addr = primary_if->net_dev->dev_addr;
+	struct batadv_bla_backbone_gw *backbone_gw;
 	u16 backbone_crc;
 	bool is_own;
 	void *hdr;
@@ -2145,32 +2146,35 @@ batadv_bla_claim_dump_entry(struct sk_buff *msg, u32 portid,
 
 	genl_dump_check_consistent(cb, hdr);
 
-	is_own = batadv_compare_eth(claim->backbone_gw->orig,
-				    primary_addr);
+	backbone_gw = batadv_bla_claim_get_backbone_gw(claim);
+
+	is_own = batadv_compare_eth(backbone_gw->orig, primary_addr);
 
-	spin_lock_bh(&claim->backbone_gw->crc_lock);
-	backbone_crc = claim->backbone_gw->crc;
-	spin_unlock_bh(&claim->backbone_gw->crc_lock);
+	spin_lock_bh(&backbone_gw->crc_lock);
+	backbone_crc = backbone_gw->crc;
+	spin_unlock_bh(&backbone_gw->crc_lock);
 
 	if (is_own)
 		if (nla_put_flag(msg, BATADV_ATTR_BLA_OWN)) {
 			genlmsg_cancel(msg, hdr);
-			goto out;
+			goto put_backbone_gw;
 		}
 
 	if (nla_put(msg, BATADV_ATTR_BLA_ADDRESS, ETH_ALEN, claim->addr) ||
 	    nla_put_u16(msg, BATADV_ATTR_BLA_VID, claim->vid) ||
 	    nla_put(msg, BATADV_ATTR_BLA_BACKBONE, ETH_ALEN,
-		    claim->backbone_gw->orig) ||
+		    backbone_gw->orig) ||
 	    nla_put_u16(msg, BATADV_ATTR_BLA_CRC,
 			backbone_crc)) {
 		genlmsg_cancel(msg, hdr);
-		goto out;
+		goto put_backbone_gw;
 	}
 
 	genlmsg_end(msg, hdr);
 	ret = 0;
 
+put_backbone_gw:
+	batadv_backbone_gw_put(backbone_gw);
 out:
 	return ret;
 }
@@ -2448,6 +2452,7 @@ int batadv_bla_backbone_dump(struct sk_buff *msg, struct netlink_callback *cb)
 bool batadv_bla_check_claim(struct batadv_priv *bat_priv,
 			    u8 *addr, unsigned short vid)
 {
+	struct batadv_bla_backbone_gw *backbone_gw;
 	struct batadv_bla_claim search_claim;
 	struct batadv_bla_claim *claim = NULL;
 	struct batadv_hard_iface *primary_if = NULL;
@@ -2470,9 +2475,13 @@ bool batadv_bla_check_claim(struct batadv_priv *bat_priv,
 	 * return false.
 	 */
 	if (claim) {
-		if (!batadv_compare_eth(claim->backbone_gw->orig,
+		backbone_gw = batadv_bla_claim_get_backbone_gw(claim);
+
+		if (!batadv_compare_eth(backbone_gw->orig,
 					primary_if->net_dev->dev_addr))
 			ret = false;
+
+		batadv_backbone_gw_put(backbone_gw);
 		batadv_claim_put(claim);
 	}
 
-- 
2.47.3


^ permalink raw reply related

* Re: [PATCH net v6 4/4] macsec: Support VLAN-filtering lower devices
From: Sabrina Dubroca @ 2026-04-08 11:01 UTC (permalink / raw)
  To: Cosmin Ratiu
  Cc: andrew+netdev@lunn.ch, davem@davemloft.net,
	linux-kselftest@vger.kernel.org, Dragos Tatulea, sdf@fomichev.me,
	shuah@kernel.org, kuba@kernel.org, pabeni@redhat.com,
	horms@kernel.org, edumazet@google.com, netdev@vger.kernel.org
In-Reply-To: <3aab1732c62fa8241ea91f2e26720ba47d646a3a.camel@nvidia.com>

2026-04-08, 10:24:28 +0000, Cosmin Ratiu wrote:
> On Wed, 2026-04-08 at 10:25 +0200, Sabrina Dubroca wrote:
> > 2026-04-07, 15:07:47 +0000, Cosmin Ratiu wrote:
> > > On Thu, 2026-04-02 at 16:48 +0200, Sabrina Dubroca wrote:
> > > > If this happens on a real device, the VLAN filters will be
> > > > broken.
> > > > I'm
> > > > not sure what the right behavior would be:
> > > > 
> > > > 1. reject the request to enable offload
> > > > 2. switch to promiscuous mode
> > > 
> > > I implemented and tested option 1. In the unlikely scenario adding
> > > VLAN
> > > filters prevents offloading
> > 
> > How unlikely is it? Resource allocation, talking to HW, device
> > limits,
> > anything else that could fail?
> > 
> > > , it's better for the driver to be explicit
> > > and let the user turn on promisc mode themselves. Keeping track of
> > > whether VLAN filters failed and promisc was used as a fallback adds
> > > some extra complexity.
> > 
> > If it's indeed (very) unlikely, sure. There's still a bit of a
> > regression/change of behavior for users compared to before the
> > IFF_UNICAST_FLT patch, but I think we can wait until someone
> > complains, and then add the tracking if that happens.
> > 
> > > What would be the point of IFF_UNICAST_FLT then?
> > 
> > The point is that this would just be a fallback.
> > 
> > > Please let me know if you agree with this approach, so I can send
> > > v8
> > > with it.
> > 
> > If you can confirm it's on the "very unlikely" side, yes, this
> > approach sounds ok. Thanks.
> 
> There are many, many drivers which implement .ndo_vlan_rx_add_vid, I
> can't really browse them all and determine overall likelihood of

Sorry, I wasn't requesting that much work, more of a "feeling" based
on your understanding. Anyway for macsec offload we don't have to care
about the vast majority of drivers.

> failure. But presumably there are some memory allocations involved and
> some FW communication to add the new filter.

I guess if FW communication fails we're in trouble anyway [*], and memory
allocation failure is an acceptable reason to reject offloading
anyway.

[*] rolling back the mdo_add_secy could be a problem if we had
difficulties talking to the HW?

> An AI prompt of "Explore implementations of .ndo_vlan_rx_add_vid in
> drivers and classify potential failure sources and their likelihood of
> failure." resulted in (edited for compactness):
> """
> Analyzed 24+ implementations across major drivers. Here's the
> breakdown:  
> Category   Likelihood Drivers                         Typical Error
> HW/FW cmds Low        mlx4, nfp, hinic, qeth, vmxnet3 Device-specific
> Mem alloc  Very Low   bnx2x, qede, virtio_net         -ENOMEM
> Filter add Low-Medium mlx5, mlx4, ice, qede, ocelot   -ENOSPC, -EEXIST
> Locking    Very Low   be2net, mlx4, nfp, hinic        -EBUSY
> Res limit  Medium     qede (quota check)              Promisc fallback
> 
> Three Driver Archetypes  
> 
> 1. Simple bitfield drivers (never fail): e1000, e1000e, ixgbe, igb —
> just set a VFTA bit in hardware registers, always return 0.
> 
> 2. Complex offload drivers (can fail): mlx5 (flow steering rules), mlx4
> (FW VLAN registration), ice (switch rules + promisc management), qede
> (quota-aware with promisc fallback), nfp (mailbox commands), hinic (FW
> commands with rollback).
> 
> 3. Delegation/passthrough drivers (inherit failures): bonding, team,
> hsr, dsa, macvlan, ipvlan, macsec — propagate vlan_vid_add() to lower
> devices with unwind-on-failure patterns.
> """
> 
> So I guess we go with option 1 for now.

ACK

-- 
Sabrina

^ permalink raw reply

* Re: [net,PATCH] net: ks8851: Reinstate disabling of BHs around IRQ handler
From: Nicolai Buchwitz @ 2026-04-08 10:54 UTC (permalink / raw)
  To: Marek Vasut
  Cc: netdev, stable, David S. Miller, Andrew Lunn, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Ronald Wahl, Yicong Hui,
	linux-kernel
In-Reply-To: <20260407212344.80265-1-marex@nabladev.com>

On 7.4.2026 23:23, Marek Vasut wrote:

> [...]

> 
> diff --git a/drivers/net/ethernet/micrel/ks8851_common.c 
> b/drivers/net/ethernet/micrel/ks8851_common.c
> index 8048770958d60..dadedea016fac 100644
> --- a/drivers/net/ethernet/micrel/ks8851_common.c
> +++ b/drivers/net/ethernet/micrel/ks8851_common.c
> @@ -316,6 +316,7 @@ static irqreturn_t ks8851_irq(int irq, void *_ks)
>  	unsigned int status;
>  	struct sk_buff *skb;
> 
> +	local_bh_disable();
>  	ks8851_lock(ks, &flags);

This breaks the SPI variant on non-RT. The SPI path sleeps in
spi_sync() -> wait_for_completion_timeout(), which can't be
done with BH disabled. Confirmed on hardware (KS8851 SPI on
CM4S, PREEMPT non-RT):

   BUG: scheduling while atomic: irq/38-eth2/708/0x00000201
   ...
   spi_transfer_one_message+0x518/0x770
   __spi_pump_transfer_message+0x1dc/0x5f0
   __spi_sync+0x2b4/0x460
   spi_sync+0x38/0x68
   ks8851_rdfifo_spi+0x60/0xc0
   ks8851_irq+0x310/0x3c8

The fix needs to be PAR-specific since the SPI variant doesn't
have the deadlock problem anyway (ks8851_start_xmit_spi doesn't
take the lock).

> 
>  	status = ks8851_rdreg16(ks, KS_ISR);
> @@ -381,6 +382,7 @@ static irqreturn_t ks8851_irq(int irq, void *_ks)
>  	if (status & IRQ_RXI)
>  		while ((skb = __skb_dequeue(&rxq)))
>  			netif_rx(skb);
> +	local_bh_enable();
> 
>  	return IRQ_HANDLED;
>  }

In order to make this work I would propose something like this (which 
works in my SPI setup):

--- a/drivers/net/ethernet/micrel/ks8851_par.c
+++ b/drivers/net/ethernet/micrel/ks8851_par.c
@@ -60,12 +60,14 @@ static void ks8851_lock_par(struct ks8851_net *ks, 
unsigned long *flags)
  {
  	struct ks8851_net_par *ksp = to_ks8851_par(ks);

+	local_bh_disable();
  	spin_lock_irqsave(&ksp->lock, *flags);
  }

  static void ks8851_unlock_par(struct ks8851_net *ks, unsigned long 
*flags)
  {
  	struct ks8851_net_par *ksp = to_ks8851_par(ks);

  	spin_unlock_irqrestore(&ksp->lock, *flags);
+	local_bh_enable();
  }

Tested-by: Nicolai Buchwitz <nb@tipi-net.de>  # KS8851 SPI, non-RT 
(regression + proposed fix)

^ permalink raw reply

* Re: [PATCH net v4 15/15] rxrpc: fix reference count leak in rxrpc_server_keyring()
From: David Howells @ 2026-04-08 10:53 UTC (permalink / raw)
  To: Anderson Nascimento
  Cc: dhowells, netdev, Marc Dionne, Jakub Kicinski, David S. Miller,
	Eric Dumazet, Paolo Abeni, linux-afs, linux-kernel, Luxiao Xu,
	Yifan Wu, Juefei Pu, Yuan Tan, Xin Liu, Ren Wei, Ren Wei,
	Simon Horman, stable
In-Reply-To: <2234233.1775645449@warthog.procyon.org.uk>

David Howells <dhowells@redhat.com> wrote:

> Anderson Nascimento <anderson@allelesecurity.com> wrote:
> 
> > Isn't this the same issue addressed by my patch "[PATCH net v4 08/15]
> > rxrpc: Fix keyring reference count leak in rxrpc_setsockopt()"? Just
> > asking to make sure this is intended.
> 
> Actually, I think it's probably better to use Luxiao Xu's patch rather than
> yours as it puts the check down into the function that actually makes the
> change.

I also then need to remove the rx->key check.

David


^ permalink raw reply

* Re: [PATCH net v4 15/15] rxrpc: fix reference count leak in rxrpc_server_keyring()
From: David Howells @ 2026-04-08 10:50 UTC (permalink / raw)
  To: Anderson Nascimento
  Cc: dhowells, netdev, Marc Dionne, Jakub Kicinski, David S. Miller,
	Eric Dumazet, Paolo Abeni, linux-afs, linux-kernel, Luxiao Xu,
	Yifan Wu, Juefei Pu, Yuan Tan, Xin Liu, Ren Wei, Ren Wei,
	Simon Horman, stable
In-Reply-To: <aa7aeb86-2430-4c67-9d5d-bb27ad037865@allelesecurity.com>

Anderson Nascimento <anderson@allelesecurity.com> wrote:

> Isn't this the same issue addressed by my patch "[PATCH net v4 08/15] rxrpc:
> Fix keyring reference count leak in rxrpc_setsockopt()"? Just asking to make
> sure this is intended.

Actually, I think it's probably better to use Luxiao Xu's patch rather than
yours as it puts the check down into the function that actually makes the
change.

David


^ permalink raw reply

* Re: [PATCH net v4 15/15] rxrpc: fix reference count leak in rxrpc_server_keyring()
From: David Howells @ 2026-04-08 10:49 UTC (permalink / raw)
  To: Jakub Kicinski
  Cc: dhowells, Anderson Nascimento, netdev, Marc Dionne,
	David S. Miller, Eric Dumazet, Paolo Abeni, linux-afs,
	linux-kernel, Luxiao Xu, Yifan Wu, Juefei Pu, Yuan Tan, Xin Liu,
	Ren Wei, Ren Wei, Simon Horman, stable
In-Reply-To: <20260401192244.6ad86fc9@kernel.org>

Jakub Kicinski <kuba@kernel.org> wrote:

> And in return maybe you can scan the AI output and tell me
> if any of it is legit? ;)
> 
> https://sashiko.dev/#/patchset/20260401105614.1696001-10-dhowells@redhat.com

PATCH 2
-------
The complaint it makes about overflows in relation to patch 2:

	net/rxrpc/key.c:rxrpc_preparse_xdr_yfs_rxgk() {
	    ...
	    keylen = ntohl(key[-1]);
	    _debug("keylen: %x", keylen);
	    keylen = round_up(keylen, 4);
	    ...
	    tktlen = ntohl(ticket[-1]);
	    _debug("tktlen: %x", tktlen);
	    tktlen = round_up(tktlen, 4);
	    ...
	}

is actually addressed by:

	[PATCH net v4 05/15] rxrpc: Fix RxGK token loading to check bounds

I guess it doesn't look forward through the patches?


PATCH 8
-------
In regards to patch 8, it asks:

	Does this change introduce an asymmetric behavior between configuring
	RXRPC_SECURITY_KEY and RXRPC_SECURITY_KEYRING?

Yes, but it's then correct.  An AF_RXRPC server socket can also make client
calls - and, indeed, the kernel AFS client requires this.  The kernel AFS
client makes filesystem access/manipulation client calls but also listens for,
amongst other things, change notifications from the server.

With regards to its complaint about patch 12, I should really switch to using
lib/crypto/ to avoid using any memory allocation, but, yes, it has a point and
that crypto_skcipher_decrypt() (and encrypt) can fail too.

"sp->len < 8" is checked for at the beginning of the functions
rxkad_verify_packet_2(), so it must be at least one block in size.  rxkad uses
pcbc to wrap fcrypt which should take care of the non-block alignment.

I'll add a patch to add error handling.


PATCH 13
--------
For patch 13, it says:

	Are there integer overflows in rxgk_verify_response() when handling
	token_len?

Yeah - I do the round up before the check.  I'll add a patch to check that the
raw token_len <= len too (len must be < 65536 as the response must fit in a
single UDP packet).

It also says:

	Does rxgk_verify_response() leak the rxgk_context structure (gk)?

Yep.  Another patch for that.

And:

	Does rxgk_do_verify_authenticator() still perform an out-of-bounds
	read if the authenticator is smaller than 24 bytes?

Unfortunately, yes.  Add one for that.

Further, it says:

	Can modifying conn->channels[i].call_counter here cause a data race?

It shouldn't, since the value is only used to allocate the number for a call
that is set in callNumber in the Rx header for that call (well, the number may
also be copied inside the encrypted payload) but it's only *interpreted* by
the peer.

The way Rx works is that there's a separate callNumber space on each channel
on each connection.  Only one call can be in progress on a given channel (the
channel number is in the bottom two bits of the connection ID field), and
calls on a channel are numbered consecutively.  Seeing a new incoming call
with the next higher callNumber implicitly completes and ACKs the previous
call on that channel (potentially rendering explicit ACK packets unnecessary
on a busy channel).

If, however, a connection becomes idle, the server can just discard its record
of it.  Should the client resume activity on that connection, the RESPONSE
packet conveys the client's call counter for each channel, from which the
server reinitialises the counters.

rxrpc_expose_client_call() cranks the counter for the client side;
rxgk_do_verify_authenticator() sets the counter for the server side.  This
sets the expectation in a secure environment of what callNumbers should be
next on a connection to help prevent replay attacks.

If the server sees the call ID revert more than 1 (to allow for duplicate
packets from the previous call), it should abort the connection.

So I think nothing needs to be fixed here, as a secure connection isn't
allowed to proceed until the RESPONSE packet is fully parsed and the transport
security set up.


PATCH 14
--------
For patch 14, it says:

	While this fixes the panic for auth_len, can a maliciously large
	token_len (e.g., 0xFFFFFFFF) cause an integer overflow in the same
	function that leads to the exact same BUG_ON() panic?

but this is the same as the first thing it says against patch 13.  I've added
a patch to check token_len.

Ditto for:

	Does this function also unconditionally leak the rxgk_context
	allocated for the transport key?

It then says:

	Are there memory leaks and data races if duplicate RESPONSE packets
	are processed concurrently?

That can't happen as there's a single work item, conn->processor, that
processes all connection-level events, including CHALLENGE and RESPONSE
issuing and parsing for that connection.  Each connection has its own totally
independent security context, so there shouldn't be interference between two
connections.

And finally, it says:

	If rxrpc_process_event() does not verify if the connection is already
	secured before invoking the verify_response() callback, duplicate
	packets would cause rxgk_verify_response() to unconditionally
	overwrite conn->key with the new key, leaking the previous key's
	reference.

	Additionally, if rxgk_init_connection_security() unconditionally
	overwrites conn->rxgk.keys[] without putting the old key or holding
	conn->security_use_lock, could this cause another rxgk_context leak
	and race against concurrent read accesses in rxgk_get_key()?

These are both addressed by a patch I've been sent that I'll add.


PATCH 15
--------
Regarding patch 15, which provides an alternative fix to patch 8, I previously
asked you to drop patch 15 - but I'm thinking now it's probably better to keep
patch 15 and drop patch 8 (and change patch 15 to return -EINVAL).

David


^ permalink raw reply

* Re: [PATCH v10 net-next 3/6] devlink: Implement devlink param multi attribute nested data values
From: Ratheesh Kannoth @ 2026-04-08 10:41 UTC (permalink / raw)
  To: Paolo Abeni
  Cc: netdev, linux-kernel, linux-rdma, sgoutham, andrew+netdev, davem,
	edumazet, kuba, donald.hunter, horms, jiri, chuck.lever, matttbe,
	cjubran, saeedm, leon, tariqt, mbloch, dtatulea
In-Reply-To: <c14a0783-a69f-448d-a464-2d802e6d0ec7@redhat.com>

On 2026-04-07 at 15:28:09, Paolo Abeni (pabeni@redhat.com) wrote:
> On 4/3/26 4:55 AM, Ratheesh Kannoth wrote:
> > From: Saeed Mahameed <saeedm@nvidia.com>
> >
> > Devlink param value attribute is not defined since devlink is handling
> > the value validating and parsing internally, this allows us to implement
> > multi attribute values without breaking any policies.
> >
> > Devlink param multi-attribute values are considered to be dynamically
> > sized arrays of u64 values, by introducing a new devlink param type
> > DEVLINK_PARAM_TYPE_U64_ARRAY, driver and user space can set a variable
> > count of u32 values into the DEVLINK_ATTR_PARAM_VALUE_DATA attribute.
> >
> > Implement get/set parsing and add to the internal value structure passed
> > to drivers.
> >
> > This is useful for devices that need to configure a list of values for
> > a specific configuration.
> >
> > example:
> > $ devlink dev param show pci/... name multi-value-param
> > name multi-value-param type driver-specific
> > values:
> > cmode permanent value: 0,1,2,3,4,5,6,7
> >
> > $ devlink dev param set pci/... name multi-value-param \
> > 		value 4,5,6,7,0,1,2,3 cmode permanent
> >
> > Signed-off-by: Saeed Mahameed <saeedm@nvidia.com>
> > Signed-off-by: Ratheesh Kannoth <rkannoth@marvell.com>
> > ---
> >  Documentation/netlink/specs/devlink.yaml |  4 ++
> >  include/net/devlink.h                    |  8 +++
> >  include/uapi/linux/devlink.h             |  1 +
> >  net/devlink/netlink_gen.c                |  2 +
> >  net/devlink/param.c                      | 91 +++++++++++++++++++-----
> >  5 files changed, 89 insertions(+), 17 deletions(-)
> >
> > diff --git a/Documentation/netlink/specs/devlink.yaml b/Documentation/netlink/specs/devlink.yaml
> > index b495d56b9137..b619de4fe08a 100644
> > --- a/Documentation/netlink/specs/devlink.yaml
> > +++ b/Documentation/netlink/specs/devlink.yaml
> > @@ -226,6 +226,10 @@ definitions:
> >          value: 10
> >        -
> >          name: binary
> > +      -
> > +        name: u64-array
> > +        value: 129
> > +
> >    -
> >      name: rate-tc-index-max
> >      type: const
> > diff --git a/include/net/devlink.h b/include/net/devlink.h
> > index 3038af6ec017..3a355fea8189 100644
> > --- a/include/net/devlink.h
> > +++ b/include/net/devlink.h
> > @@ -432,6 +432,13 @@ enum devlink_param_type {
> >  	DEVLINK_PARAM_TYPE_U64 = DEVLINK_VAR_ATTR_TYPE_U64,
> >  	DEVLINK_PARAM_TYPE_STRING = DEVLINK_VAR_ATTR_TYPE_STRING,
> >  	DEVLINK_PARAM_TYPE_BOOL = DEVLINK_VAR_ATTR_TYPE_FLAG,
> > +	DEVLINK_PARAM_TYPE_U64_ARRAY = DEVLINK_VAR_ATTR_TYPE_U64_ARRAY,
> > +};
> > +
> > +#define __DEVLINK_PARAM_MAX_ARRAY_SIZE 32
> > +struct devlink_param_u64_array {
> > +	u64 size;
> > +	u64 val[__DEVLINK_PARAM_MAX_ARRAY_SIZE];
> >  };
> >
> >  union devlink_param_value {
> > @@ -441,6 +448,7 @@ union devlink_param_value {
> >  	u64 vu64;
> >  	char vstr[__DEVLINK_PARAM_MAX_STRING_VALUE];
> >  	bool vbool;
> > +	struct devlink_param_u64_array u64arr;
>
> Sashiko as a couple of relevant remarks here, specifically:
>
> ---
> Does this increase the size of union devlink_param_value from 32 bytes
> to over 264 bytes?
> Looking at existing functions like devlink_nl_param_value_fill_one() and
> devlink_nl_param_value_put(), they take multiple copies of this union by
> value. Passing two of these unions by value consumes over 528 bytes of
> stack space, and combined in a call chain this pushes nearly 800 bytes
> of arguments onto the stack.
> Could this create a risk of hitting CONFIG_FRAME_WARN limits deep in
> driver notification contexts? Should the signatures of the internal
> functions and exported APIs be updated to pass the unions by pointer
> instead?
ACK. will make as seperate patch.

> ---
>
> >  };
> >
> >  struct devlink_param_gset_ctx {
> > diff --git a/include/uapi/linux/devlink.h b/include/uapi/linux/devlink.h
> > index 7de2d8cc862f..5332223dd6d0 100644
> > --- a/include/uapi/linux/devlink.h
> > +++ b/include/uapi/linux/devlink.h
> > @@ -406,6 +406,7 @@ enum devlink_var_attr_type {
> >  	DEVLINK_VAR_ATTR_TYPE_BINARY,
> >  	__DEVLINK_VAR_ATTR_TYPE_CUSTOM_BASE = 0x80,
> >  	/* Any possible custom types, unrelated to NLA_* values go below */
> > +	DEVLINK_VAR_ATTR_TYPE_U64_ARRAY,
> >  };
> >
> >  enum devlink_attr {
> > diff --git a/net/devlink/netlink_gen.c b/net/devlink/netlink_gen.c
> > index eb35e80e01d1..7aaf462f27ee 100644
> > --- a/net/devlink/netlink_gen.c
> > +++ b/net/devlink/netlink_gen.c
> > @@ -37,6 +37,8 @@ devlink_attr_param_type_validate(const struct nlattr *attr,
> >  	case DEVLINK_VAR_ATTR_TYPE_NUL_STRING:
> >  		fallthrough;
> >  	case DEVLINK_VAR_ATTR_TYPE_BINARY:
> > +		fallthrough;
> > +	case DEVLINK_VAR_ATTR_TYPE_U64_ARRAY:
> >  		return 0;
> >  	}
> >  	NL_SET_ERR_MSG_ATTR(extack, attr, "invalid enum value");
> > diff --git a/net/devlink/param.c b/net/devlink/param.c
> > index cf95268da5b0..2ec85dffd8ac 100644
> > --- a/net/devlink/param.c
> > +++ b/net/devlink/param.c
> > @@ -252,6 +252,14 @@ devlink_nl_param_value_put(struct sk_buff *msg, enum devlink_param_type type,
> >  				return -EMSGSIZE;
> >  		}
> >  		break;
> > +	case DEVLINK_PARAM_TYPE_U64_ARRAY:
> > +		if (val.u64arr.size > __DEVLINK_PARAM_MAX_ARRAY_SIZE)
> > +			return -EMSGSIZE;
> > +
> > +		for (int i = 0; i < val.u64arr.size; i++)
> > +			if (nla_put_uint(msg, nla_type, val.u64arr.val[i]))
> > +				return -EMSGSIZE;
> > +		break;
> >  	}
> >  	return 0;
> >  }
> > @@ -304,56 +312,78 @@ static int devlink_nl_param_fill(struct sk_buff *msg, struct devlink *devlink,
> >  				 u32 portid, u32 seq, int flags,
> >  				 struct netlink_ext_ack *extack)
> >  {
> > -	union devlink_param_value default_value[DEVLINK_PARAM_CMODE_MAX + 1];
> > -	union devlink_param_value param_value[DEVLINK_PARAM_CMODE_MAX + 1];
> >  	bool default_value_set[DEVLINK_PARAM_CMODE_MAX + 1] = {};
> >  	bool param_value_set[DEVLINK_PARAM_CMODE_MAX + 1] = {};
> >  	const struct devlink_param *param = param_item->param;
> > -	struct devlink_param_gset_ctx ctx;
> > +	union devlink_param_value *default_value;
> > +	union devlink_param_value *param_value;
> > +	struct devlink_param_gset_ctx *ctx;
> >  	struct nlattr *param_values_list;
> >  	struct nlattr *param_attr;
> >  	void *hdr;
> >  	int err;
> >  	int i;
> >
> > +	default_value = kcalloc(DEVLINK_PARAM_CMODE_MAX + 1,
> > +				sizeof(*default_value), GFP_KERNEL);
> > +	if (!default_value)
> > +		return -ENOMEM;
> > +
> > +	param_value = kcalloc(DEVLINK_PARAM_CMODE_MAX + 1,
> > +			      sizeof(*param_value), GFP_KERNEL);
> > +	if (!param_value) {
> > +		kfree(default_value);
> > +		return -ENOMEM;
> > +	}
> > +
> > +	ctx = kmalloc_obj(*ctx);
> > +	if (!ctx) {
> > +		kfree(param_value);
> > +		kfree(default_value);
> > +		return -ENOMEM;
> > +	}
> > +
> >  	/* Get value from driver part to driverinit configuration mode */
> >  	for (i = 0; i <= DEVLINK_PARAM_CMODE_MAX; i++) {
> >  		if (!devlink_param_cmode_is_supported(param, i))
> >  			continue;
> >  		if (i == DEVLINK_PARAM_CMODE_DRIVERINIT) {
> > -			if (param_item->driverinit_value_new_valid)
> > +			if (param_item->driverinit_value_new_valid) {
> >  				param_value[i] = param_item->driverinit_value_new;
> > -			else if (param_item->driverinit_value_valid)
> > +			} else if (param_item->driverinit_value_valid) {
> >  				param_value[i] = param_item->driverinit_value;
> > -			else
> > -				return -EOPNOTSUPP;
> > +			} else {
> > +				err = -EOPNOTSUPP;
> > +				goto get_put_fail;
> > +			}
> >
> >  			if (param_item->driverinit_value_valid) {
> >  				default_value[i] = param_item->driverinit_default;
> >  				default_value_set[i] = true;
> >  			}
> >  		} else {
> > -			ctx.cmode = i;
> > -			err = devlink_param_get(devlink, param, &ctx, extack);
> > +			ctx->cmode = i;
> > +			err = devlink_param_get(devlink, param, ctx, extack);
> >  			if (err)
> > -				return err;
> > -			param_value[i] = ctx.val;
> > +				goto get_put_fail;
> > +			param_value[i] = ctx->val;
> >
> > -			err = devlink_param_get_default(devlink, param, &ctx,
> > +			err = devlink_param_get_default(devlink, param, ctx,
> >  							extack);
> >  			if (!err) {
> > -				default_value[i] = ctx.val;
> > +				default_value[i] = ctx->val;
> >  				default_value_set[i] = true;
> >  			} else if (err != -EOPNOTSUPP) {
> > -				return err;
> > +				goto get_put_fail;
> >  			}
> >  		}
> >  		param_value_set[i] = true;
> >  	}
> >
> > +	err = -EMSGSIZE;
> >  	hdr = genlmsg_put(msg, portid, seq, &devlink_nl_family, flags, cmd);
> >  	if (!hdr)
> > -		return -EMSGSIZE;
> > +		goto get_put_fail;
> >
> >  	if (devlink_nl_put_handle(msg, devlink))
> >  		goto genlmsg_cancel;
> > @@ -393,6 +423,9 @@ static int devlink_nl_param_fill(struct sk_buff *msg, struct devlink *devlink,
> >  	nla_nest_end(msg, param_values_list);
> >  	nla_nest_end(msg, param_attr);
> >  	genlmsg_end(msg, hdr);
> > +	kfree(default_value);
> > +	kfree(param_value);
> > +	kfree(ctx);
> >  	return 0;
> >
> >  values_list_nest_cancel:
> > @@ -401,7 +434,11 @@ static int devlink_nl_param_fill(struct sk_buff *msg, struct devlink *devlink,
> >  	nla_nest_cancel(msg, param_attr);
> >  genlmsg_cancel:
> >  	genlmsg_cancel(msg, hdr);
> > -	return -EMSGSIZE;
> > +get_put_fail:
> > +	kfree(default_value);
> > +	kfree(param_value);
> > +	kfree(ctx);
> > +	return err;
> >  }
> >
> >  static void devlink_param_notify(struct devlink *devlink,
> > @@ -507,7 +544,7 @@ devlink_param_value_get_from_info(const struct devlink_param *param,
> >  				  union devlink_param_value *value)
> >  {
> >  	struct nlattr *param_data;
> > -	int len;
> > +	int len, cnt, rem;
> >
> >  	param_data = info->attrs[DEVLINK_ATTR_PARAM_VALUE_DATA];
> >
> > @@ -547,6 +584,26 @@ devlink_param_value_get_from_info(const struct devlink_param *param,
> >  			return -EINVAL;
> >  		value->vbool = nla_get_flag(param_data);
> >  		break;
> > +
> > +	case DEVLINK_PARAM_TYPE_U64_ARRAY:
> > +		cnt = 0;
> > +		nla_for_each_attr_type(param_data,
> > +				       DEVLINK_ATTR_PARAM_VALUE_DATA,
> > +				       genlmsg_data(info->genlhdr),
> > +				       genlmsg_len(info->genlhdr), rem) {
> > +			if (cnt >= __DEVLINK_PARAM_MAX_ARRAY_SIZE)
> > +				return -EMSGSIZE;
> > +
> > +			if ((nla_len(param_data) != sizeof(u64)) &&
> > +			    (nla_len(param_data) != sizeof(u32)))
> > +				return -EINVAL;
> > +
> > +			value->u64arr.val[cnt] = (u64)nla_get_uint(param_data);
> > +			cnt++;
> > +		}
> > +
> > +		value->u64arr.size = cnt;
> > +		break;
>
> Sashiko says:
>
> ---
> Does this make it impossible to set an empty array to clear a
> multi-value parameter?
> If userspace provides 0 elements, param_data will be NULL. Earlier in
> devlink_param_value_get_from_info(), there is a check:
> 	param_data = info->attrs[DEVLINK_ATTR_PARAM_VALUE_DATA];
> 	if (param->type != DEVLINK_PARAM_TYPE_BOOL && !param_data)
> 		return -EINVAL;
> If the parameter is a U64_ARRAY and no data is provided, this check will
> immediately return -EINVAL.
> The kernel can successfully emit an empty array on a GET request if the
> size is 0. Should the SET path similarly support receiving 0 elements to
> allow userspace to clear a multi-value parameter?
multi-value parameter is to set values. if some one wants to set to zero
they can set all values as zero and pass a "size" array. we can ignore this comment, right ?

> ---
>
> There are several others NIC-specific remarks, which IMHO are mostly
> pre-existing issues, but please have a look:
>
> https://sashiko.dev/#/patchset/20260403025533.6250-1-rkannoth%40marvell.com
>
> /P
>

A. Patch 1: [PATCH v10 net-next 1/6] octeontx2-af: npc: cn20k: debugfs enhancements

1.

"Is it safe to use a global static array here?
Concurrent reads of the dstats debugfs file could race to read and write to
this array, leading to torn updates. Additionally, on systems with multiple
RVU devices, wouldn't they share the same array and cross-contaminate their
statistics?"

The purpose of using "dstats" is to identify which rules current traffic is hitting.
We are not particularly concerned with overall packet statistics, as those can
be obtained through rule's stats register. Each rule hit will display the corresponding
rule during 'cating', so this comment can be disregarded.

2.

"Are the hardware parameters read into npc_priv->bank_depth and
npc_priv->num_banks guaranteed to fit within the static bounds of dstats?
If the hardware reports a bank_depth that makes mcam_idx exceed the
pre-allocated array dimensions, could this result in an out-of-bounds
memory access here?"

static u64 dstats[MAX_NUM_BANKS][MAX_SUBBANK_DEPTH * MAX_NUM_SUB_BANKS] = {};
MAX_SUBBANK_DEPTH and MAX_NUM_SUB_BANKS are maximum possible. So above case
wont happen

3.

"Could this cause data loss if the output exceeds the initial seq_file
buffer size?
DEFINE_SHOW_ATTRIBUTE() uses single_open(), which will discard the buffer,
double its size, and re-invoke this show callback from the beginning if it
overflows.
Since dstats[bank][idx] is updated during the first execution, a
re-invocation would see stats == dstats[bank][idx] and silently drop all
previously processed entries."

The purpose of "dstats" entry to find live traffic hitting rules. It may be showing "wrong"
value for the first time, but second time onwards, it will be okay. Usually user runs 'cat'ing mutliple
times to find "htting" rule.

5.

"Similar to the dstats array, does this code overflow en_map?
If the index provided here can exceed the compile-time bounds of
MAX_NUM_BANKS * MAX_NUM_SUB_BANKS * MAX_SUBBANK_DEPTH due to dynamically
read hardware sizes, it could corrupt memory.
Additionally, is en_map explicitly cleared during device initialization
or teardown?
Since npc_priv is global, if the driver is unbound and rebound without
unloading the module, could en_map retain stale state from the previous
session?"
MAX_NUM_BANKS * MAX_NUM_SUB_BANKS * MAX_SUBBANK_DEPTH is big enough to hold
for all SoC variants.

B. Patch 2: [PATCH v10 net-next 2/6] net/mlx5e: heap-allocate devlink param values

1.

"Does returning -ENOMEM here trigger a redundant WARN_ON() in the caller?
Looking at mlx5e_pcie_cong_event_init(), it checks the return value like this:
drivers/net/ethernet/mellanox/mlx5/core/en/pcie_cong_event.c:mlx5e_pcie_cong_event_init() {
    ...
	err = mlx5e_pcie_cong_get_thresh_config(mdev, &thresh_config);
	if (WARN_ON(err))
		return err;
    ...
}
Since kcalloc() already prints a warning and stack trace on allocation failure
(unless __GFP_NOWARN is used), wrapping the -ENOMEM return in WARN_ON() will
result in a double stack trace in the kernel log.
Is it possible to drop the WARN_ON() in the caller to avoid this redundant
splat?"

Existing code issue.  i believe, i dont have to fix this. correct me pls.

C. Patch 3: [PATCH v10 net-next 3/6] devlink: Implement devlink param multi attribute nested data values

Replied above as part of your comments.

D. Patch 4: [PATCH v10 net-next 4/6] octeontx2-af: npc: cn20k: add subbank search order control

1.

"If xa_store() fails and we jump to fail_used, doesn't this leave the device
in an unrecoverable state?
The code previously erased all subbank entries from npc_priv.xa_sb_used and
npc_priv.xa_sb_free. By aborting without restoring the remaining subbanks,
the erased subbanks appear to be permanently leaked, which would permanently
break subsequent hardware MCAM allocations. Global state is also modified
before the insertion loop, leaving the driver's internal state inconsistent.
Could this be avoided by pre-allocating the required XArray slots with
xa_reserve() or by building new XArrays and swapping them upon success?"

We are doing this operation underlocks and there wont be repetitions of slots in
the array. This above condiition is very unlikely, if there is some issue, we
flag the error thru print. This means that there is some other deep issue which
caused this error. So we can ignore this.

2.
" +	if (bitmap_weight(&w, sz) != sz) {
> +		dev_err(rvu->dev,
> +			"Duplicate or out-of-range subbank index. %lu\n",
> +			find_first_zero_bit(&w, sz));
> +		return -EINVAL;
> +	}
Does this error message print the missing index instead of the duplicate
one?
Since find_first_zero_bit() finds the first unset bit, it will return the
index that the user failed to provide, not the one they duplicated or
provided out of range. Could this lead to a confusing error message for
users?"
I will change print error message to   "Duplicate or missing or out-of-range subbank index. %lu\n","

E. Patch 5: [PATCH v10 net-next 5/6] octeontx2-af: npc: cn20k: dynamically allocate and free default MCAM entries

1.

>  	u64 cfg, hw_prio;
>  	u8 kw_type;
>
> +	if (index < 0 || index >= mcam->total_entries) {
> +		WARN(1, "Wrong mcam index %d\n", index);
> +		return;
> +	}
Could this cause kernel log spam due to unchecked negative MCAM indices?
In npc_enadis_default_entries() and npc_enadis_default_mce_entry(), the code
queries the MCAM index using npc_get_nixlf_mcam_index(), which correctly
returns a negative error code if the entry is not allocated.
This unchecked negative index is then passed directly to
npc_cn20k_enable_mcam_entry(), which checks if (index < 0) and triggers a
stack trace via WARN(1, ...). This means that a routine NIX LF teardown of
an uninitialized interface (or a VF without multicast support querying the
PF's missing BCAST entry) will repeatedly trigger stack traces.

We have checks in npc_enadis_default_entries() to check for default entries and bailout.

       /* only CGX or LBK interfaces have default entries */
        if (is_cn20k(rvu->pdev) &&
            !npc_is_cgx_or_lbk(rvu, pcifunc & ~RVU_PFVF_FUNC_MASK))
                return;

will add the same for npc_enadis_default_mce_entry() as well.

2.

> @@ -4024,6 +4041,13 @@ int npc_cn20k_dft_rules_idx_get(struct rvu *rvu, u16 pcifunc, u16 *bcast,
>  	void *val;
>  	int i, j;
>
> +	for (int i = 0; i < ARRAY_SIZE(ptr); i++) {
> +		if (!ptr[i])
> +			continue;
There is variable shadowing here. The variable 'i' is declared at the
function scope, but the loop re-declares 'i', shadowing the outer scope
variable. This triggers -Wshadow compiler warnings.

ACK.

3.

Does this code use virtual MCAM indices as physical indices, potentially
causing massive out-of-bounds memory corruption?
In npc_cn20k_dft_rules_alloc(), default MCAM entries are allocated via the
mailbox handler (rvu_mbox_handler_npc_mcam_alloc_entry), which returns
virtual indices (vidx) in rsp.entry_list for CN20K hardware. These vidx
values are inserted directly into the xa_pf2dfl_rmap xarray.
Later, this code in npc_cn20k_dft_rules_free() retrieves these vidx values
and passes them directly to physical index APIs: npc_mcam_clear_bit() and
mcam->entry2pfvf_map[]. Because vidx indices are structurally shifted
(starting at npc_priv.bank_depth * 2), they are guaranteed to be larger
than mcam->bmap_entries.
This causes rentry = mcam->bmap_entries - index - 1 in npc_mcam_clear_bit()
to underflow, leading __clear_bit() to perform out-of-bounds writes on
kernel heap memory. The vidx values should probably be mapped back to
physical indices using npc_cn20k_vidx2idx() before accessing software state
arrays.

virt field in npc_mcam_alloc_entry_req structure is not set for while calling for
default mcam index allocation using rvu_mbox_handler_npc_mcam_alloc_entry(). So
this comment can be ignored.

4.

Is there a race condition here that could lead to MCAM index corruption due
to premature lock release?
The mcam->lock is held while calling npc_mcam_clear_bit(), which
immediately marks the MCAM index as free in the software bitmap
(mcam->bmap). The code then explicitly unlocks mcam->lock before calling
npc_cn20k_idx_free() to release the index in the firmware/hardware.
Releasing the lock creates a critical race window: another thread can
concurrently allocate this exact MCAM index (since it appears free in the
bitmap), assign it to a new interface, and configure it.
When the original thread resumes and calls npc_cn20k_idx_free(), it
instructs the hardware to free the index, silently destroying the newly
allocated flow rules of the concurrent thread. Should the hardware cleanup
be performed before the software bitmap is marked free, or should the lock
remain held?

These alloc free request are thru mbox messages and are processed one by one (serially)
So ignore this case.

5.

int npc_cn20k_dft_rules_alloc(struct rvu *rvu, u16 pcifunc)
Is there a software bitmap leak and multi-free bug in the error path of
npc_cn20k_dft_rules_alloc()?
If xa_insert() fails, the code jumps to the err label to free the allocated
entries. The error path executes:
	for (i = 0; i < cnt; i++)
		npc_idx_free(rvu, mcam_idx, cnt, false);
Since npc_idx_free() takes an array and a count to free 'cnt' entries,
looping over it causes the exact same array of indices to be freed 'cnt'
times.
Furthermore, the error path fails to clear the software bits in the core
hardware bitmap (mcam->bmap) via npc_mcam_clear_bit() and does not
invalidate mcam->entry2pfvf_map. Since the allocation handler successfully
marked these entries as used, omitting these steps permanently leaks the
software state for these MCAM indices.

dft rules fail means a critical issue as this will affect system operation. Theere is
no point in recovering cleanly from it. But anyway, i dont find a clear 1:1 with code, am
i missing anything here.

6.

Does this early return skip the cleanup of user-defined flow rules for
non-CGX/LBK interfaces?
While the intent seems to be skipping the disablement of default entries
for interfaces that don't have them (like SDP or CPT), this function is
also responsible for disabling ALL user-defined MCAM flow rules associated
with the pcifunc by iterating over mcam->mcam_rules.
By returning early here, all user-defined rules are left fully active in
the hardware parser, pointing to a NIX LF that is being torn down and freed.
This could lead to hardware DMA faults and context errors when traffic
matches these orphaned rules. Should this check be exclusively inside
rvu_npc_disable_default_entries() instead?

ACK.

7.

Can this cause a permanent resource leak of default MCAM entries on PF driver
teardown?
otx2_free_hw_resources() unconditionally sets the NIX_LF_DONT_FREE_DFT_IDXS
flag during NIX LF free. While intended for cases like suspend/resume, this
function is called across all driver unbind/remove paths.
Consequently, when the otx2_pf driver is unloaded, the AF driver skips
freeing the default MCAM rules, leaving them permanently orphaned in the
AF's xa_pf2dfl_rmap and hardware allocation bitmap. They can never be
recovered or reused by other physical or virtual functions, causing a
severe leak of globally shared MCAM resources on every module detach.

These default mcam indexes are freed during rvu_detach_rsrcs mbox call.
rvu_detach_rsrcs() ->npc_cn20k_dft_rules_free().

F: Patch 6: [PATCH v10 net-next 6/6] octeontx2-af: npc: Support for custom KPU profile from filesystem

1.

Can this corrupt global state? In npc_prepare_default_kpu(), adapter->kpu
is initialized to the global static array npc_kpu_profiles. By assigning
kpu->cam2 and kpu->action2 here, this modifies the shared static array with
device-managed memory (devm_kcalloc).
If the device is unbound, the memory will be freed, leaving dangling pointers
in the global array which could lead to a use-after-free for subsequent device
instances.

This memory is part of "AF" and is initlaized during boot. AF memory will be freed when
device is shutdown. There wont be any case of af getting freed and come again on a live system.

2.

> +
> +	action = (struct npc_kpu_profile_action *)(fw->data + offset);
> +
> +	if (rvu->kpu_fwdata_sz < hdr_sz + sizeof(ikpu_action_entries))
> +		return -ENOMEM;
Is -ENOMEM the correct return value here? A malformed or truncated firmware
file would cause this condition, so -EINVAL might be more appropriate than
indicating a memory allocation failure.

ACK.

3.

Could this cause firmware compatibility issues in the future?
ikpu_action_entries is an internal kernel array. If new entries are added
to it in future kernel versions, sizeof(ikpu_action_entries) will grow. This
would cause older firmware files to fail the size check above, or miscalculate
the offset for subsequent entries.

There could be a chance, but there is not field in "struct npc_kpu_profile_fwdata"
to indicate sizeof ikpu_action_entries. Adding a new field will break backward compatability etc.
Anyway In future if more entries are planned, we need to come up with a new design to address
this issue.

4.

 +
> +	for (kpu = 0; kpu < fw->kpus; kpu++) {
Does this loop have a bounds checking issue? Unlike the firmware database
path which checks if fw->kpus is greater than profile->kpus, this path only
checked if fw->kpus is greater than rvu->hw->npc_kpus earlier.
If the hardware limit exceeds the size of the statically allocated
profile->kpu array, this could overflow profile->kpu.

ACK.

5.

 +				 "profile size mismatch on kpu%i parsing\n",
> +				 kpu + 1);
> +			return -EINVAL;
> +		}
> +
> +		fw_kpu = (struct npc_kpu_fwdata *)(fw->data + offset);
> +		entries = min(fw_kpu->entries, rvu->hw->npc_kpu_entries);
Can this result in an out-of-bounds read? The size check above only ensures
that the start of fw_kpu is within the firmware data boundaries. It does not
verify that the entire fw_kpu structure fits within the remaining space before
dereferencing fw_kpu->entries.
> +		dev_info(rvu->dev,
> +			 "Loading %u entries on KPU%d\n", entries, kpu);
> +

ACK.

^ permalink raw reply

* Re: [PATCH net v2 1/1] net: hsr: avoid learning unknown senders for local delivery
From: Felix Maurer @ 2026-04-08 10:40 UTC (permalink / raw)
  To: Ao Zhou
  Cc: netdev, Sebastian Andrzej Siewior, David S . Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Murali Karicheri,
	Shaurya Rane, Ingo Molnar, Kees Cook, Yifan Wu, Juefei Pu,
	Yuan Tan, Xin Liu, Yuqi Xu, Haoze Xie
In-Reply-To: <b053e938014c9bac22f7f687ecc2970f23a2b74a.1775281843.git.royenheart@gmail.com>

On Sat, Apr 04, 2026 at 07:30:47PM +0800, Ao Zhou wrote:
> From: Haoze Xie <royenheart@gmail.com>
>
> Traffic that is directly addressed to the local HSR/PRP master can be
> delivered locally without creating a persistent node entry. Learning one
> node per previously unseen source MAC lets forged sender floods grow
> node_db until the prune timer catches up.
>
> Determine whether a frame is locally exclusive before node lookup and
> skip learning for unknown senders in that case. When no node state
> exists, also skip duplicate discard checks that depend on it.
>
> This keeps locally-destined traffic reachable while avoiding node table
> growth from source-MAC floods in both the PRP SAN path and the HSR/PRP
> tagged sender paths.

I see the problem you are trying to solve here, but I don't think this
patch provides a significant improvement over the current situation.
Yes, this will disable learning of new nodes from regular traffic (and
thereby completely prevent the duplicate discard algorithm from
working). New nodes would only be learned from supervision frames. But
nothing prevents a malicious host in the network from spoofing tons of
supervision frames.

HSR and PRP are supposed to be used in pretty restricted network
environments, so the whole protocol design doesn't really expect
malicious actors in the network and doesn't provide good options to
safeguard against misuse.

IMHO, the only real way to prevent excessive resource use on our side is
to put a limit on these resources. In this case, limit the size of the
node table (bonus: make that limit configurable as Paolo suggested).

Thanks,
   Felix


> Fixes: f421436a591d ("net/hsr: Add support for the High-availability Seamless Redundancy protocol (HSRv0)")
> Fixes: 451d8123f897 ("net: prp: add packet handling support")
> Reported-by: Yifan Wu <yifanwucs@gmail.com>
> Reported-by: Juefei Pu <tomapufckgml@gmail.com>
> Co-developed-by: Yuan Tan <yuantan098@gmail.com>
> Signed-off-by: Yuan Tan <yuantan098@gmail.com>
> Suggested-by: Xin Liu <bird@lzu.edu.cn>
> Tested-by: Yuqi Xu <xuyuqiabc@gmail.com>
> Signed-off-by: Haoze Xie <royenheart@gmail.com>
> Signed-off-by: Ao Zhou <n05ec@lzu.edu.cn>
> ---
> changes in v2:
> - generalize the fix beyond PRP SAN traffic and cover HSR/PRP tagged sender floods
> - decide whether learning is needed from local-exclusive delivery instead of protocol-specific SAN checks
> - use the normal NULL return semantics from hsr_get_node() instead of ERR_PTR-based error plumbing
> - skip duplicate-discard state checks when no node state exists
>
>  net/hsr/hsr_forward.c  | 23 +++++++++++++----------
>  net/hsr/hsr_framereg.c |  5 ++++-
>  net/hsr/hsr_framereg.h |  2 +-
>  3 files changed, 18 insertions(+), 12 deletions(-)
>
> diff --git a/net/hsr/hsr_forward.c b/net/hsr/hsr_forward.c
> index aefc9b6936ba..15bd17b4ee17 100644
> --- a/net/hsr/hsr_forward.c
> +++ b/net/hsr/hsr_forward.c
> @@ -403,7 +403,8 @@ static void hsr_deliver_master(struct sk_buff *skb, struct net_device *dev,
>  	int res, recv_len;
>
>  	was_multicast_frame = (skb->pkt_type == PACKET_MULTICAST);
> -	hsr_addr_subst_source(node_src, skb);
> +	if (node_src)
> +		hsr_addr_subst_source(node_src, skb);
>  	skb_pull(skb, ETH_HLEN);
>  	recv_len = skb->len;
>  	res = netif_rx(skb);
> @@ -545,7 +546,7 @@ static void hsr_forward_do(struct hsr_frame_info *frame)
>  		/* Don't send frame over port where it has been sent before.
>  		 * Also for SAN, this shouldn't be done.
>  		 */
> -		if (!frame->is_from_san &&
> +		if (frame->node_src && !frame->is_from_san &&
>  		    hsr->proto_ops->register_frame_out &&
>  		    hsr->proto_ops->register_frame_out(port, frame))
>  			continue;
> @@ -688,21 +689,25 @@ static int fill_frame_info(struct hsr_frame_info *frame,
>  		return -EINVAL;
>
>  	memset(frame, 0, sizeof(*frame));
> +	frame->port_rcv = port;
>  	frame->is_supervision = is_supervision_frame(port->hsr, skb);
>  	if (frame->is_supervision && hsr->redbox)
>  		frame->is_proxy_supervision =
>  			is_proxy_supervision_frame(port->hsr, skb);
>
> +	ethhdr = (struct ethhdr *)skb_mac_header(skb);
> +	check_local_dest(port->hsr, skb, frame);
> +
>  	n_db = &hsr->node_db;
>  	if (port->type == HSR_PT_INTERLINK)
>  		n_db = &hsr->proxy_node_db;
>
>  	frame->node_src = hsr_get_node(port, n_db, skb,
> -				       frame->is_supervision, port->type);
> -	if (!frame->node_src)
> -		return -1; /* Unknown node and !is_supervision, or no mem */
> +				       frame->is_supervision, port->type,
> +				       !frame->is_local_exclusive);
> +	if (!frame->node_src && !frame->is_local_exclusive)
> +		return -1;
>
> -	ethhdr = (struct ethhdr *)skb_mac_header(skb);
>  	frame->is_vlan = false;
>  	proto = ethhdr->h_proto;
>
> @@ -720,13 +725,10 @@ static int fill_frame_info(struct hsr_frame_info *frame,
>  	}
>
>  	frame->is_from_san = false;
> -	frame->port_rcv = port;
>  	ret = hsr->proto_ops->fill_frame_info(proto, skb, frame);
>  	if (ret)
>  		return ret;
>
> -	check_local_dest(port->hsr, skb, frame);
> -
>  	return 0;
>  }
>
> @@ -739,7 +741,8 @@ void hsr_forward_skb(struct sk_buff *skb, struct hsr_port *port)
>  	if (fill_frame_info(&frame, skb, port) < 0)
>  		goto out_drop;
>
> -	hsr_register_frame_in(frame.node_src, port, frame.sequence_nr);
> +	if (frame.node_src)
> +		hsr_register_frame_in(frame.node_src, port, frame.sequence_nr);
>  	hsr_forward_do(&frame);
>  	rcu_read_unlock();
>  	/* Gets called for ingress frames as well as egress from master port.
> diff --git a/net/hsr/hsr_framereg.c b/net/hsr/hsr_framereg.c
> index 50996f4de7f9..2bc6f8f154c2 100644
> --- a/net/hsr/hsr_framereg.c
> +++ b/net/hsr/hsr_framereg.c
> @@ -221,7 +221,7 @@ void prp_update_san_info(struct hsr_node *node, bool is_sup)
>   */
>  struct hsr_node *hsr_get_node(struct hsr_port *port, struct list_head *node_db,
>  			      struct sk_buff *skb, bool is_sup,
> -			      enum hsr_port_type rx_port)
> +			      enum hsr_port_type rx_port, bool learn)
>  {
>  	struct hsr_priv *hsr = port->hsr;
>  	struct hsr_node *node;
> @@ -270,6 +270,9 @@ struct hsr_node *hsr_get_node(struct hsr_port *port, struct list_head *node_db,
>  			san = true;
>  	}
>
> +	if (!learn)
> +		return NULL;
> +
>  	return hsr_add_node(hsr, node_db, ethhdr->h_source, san, rx_port);
>  }
>
> diff --git a/net/hsr/hsr_framereg.h b/net/hsr/hsr_framereg.h
> index c65ecb925734..3d9c88e83090 100644
> --- a/net/hsr/hsr_framereg.h
> +++ b/net/hsr/hsr_framereg.h
> @@ -33,7 +33,7 @@ void hsr_del_self_node(struct hsr_priv *hsr);
>  void hsr_del_nodes(struct list_head *node_db);
>  struct hsr_node *hsr_get_node(struct hsr_port *port, struct list_head *node_db,
>  			      struct sk_buff *skb, bool is_sup,
> -			      enum hsr_port_type rx_port);
> +			      enum hsr_port_type rx_port, bool learn);
>  void hsr_handle_sup_frame(struct hsr_frame_info *frame);
>  bool hsr_addr_is_self(struct hsr_priv *hsr, unsigned char *addr);
>  bool hsr_addr_is_redbox(struct hsr_priv *hsr, unsigned char *addr);
> --
> 2.53.0
>


^ permalink raw reply

* [PATCH net-next v3 4/4] can: raw: convert to getsockopt_iter
From: Breno Leitao @ 2026-04-08 10:30 UTC (permalink / raw)
  To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, Kuniyuki Iwashima, Willem de Bruijn, metze, axboe,
	Stanislav Fomichev
  Cc: io-uring, bpf, netdev, Linus Torvalds, linux-kernel, kernel-team,
	Breno Leitao
In-Reply-To: <20260408-getsockopt-v3-0-061bb9cb355d@debian.org>

Convert CAN raw socket's getsockopt implementation to use the new
getsockopt_iter callback with sockopt_t.

Key changes:
- Replace (char __user *optval, int __user *optlen) with sockopt_t *opt
- Use opt->optlen for buffer length (input) and returned size (output)
- Use copy_to_iter() instead of copy_to_user()
- For CAN_RAW_FILTER and CAN_RAW_XL_VCID_OPTS: on -ERANGE, set
  opt->optlen to the required buffer size. The wrapper writes this
  back to userspace even on error, preserving the existing API that
  lets userspace discover the needed allocation size.

Signed-off-by: Breno Leitao <leitao@debian.org>
---
 net/can/raw.c | 28 +++++++++++++---------------
 1 file changed, 13 insertions(+), 15 deletions(-)

diff --git a/net/can/raw.c b/net/can/raw.c
index eee244ffc31ec..6f9ef867a13f2 100644
--- a/net/can/raw.c
+++ b/net/can/raw.c
@@ -760,7 +760,7 @@ static int raw_setsockopt(struct socket *sock, int level, int optname,
 }
 
 static int raw_getsockopt(struct socket *sock, int level, int optname,
-			  char __user *optval, int __user *optlen)
+			  sockopt_t *opt)
 {
 	struct sock *sk = sock->sk;
 	struct raw_sock *ro = raw_sk(sk);
@@ -770,8 +770,7 @@ static int raw_getsockopt(struct socket *sock, int level, int optname,
 
 	if (level != SOL_CAN_RAW)
 		return -EINVAL;
-	if (get_user(len, optlen))
-		return -EFAULT;
+	len = opt->optlen;
 	if (len < 0)
 		return -EINVAL;
 
@@ -787,12 +786,12 @@ static int raw_getsockopt(struct socket *sock, int level, int optname,
 			if (len < fsize) {
 				/* return -ERANGE and needed space in optlen */
 				err = -ERANGE;
-				if (put_user(fsize, optlen))
-					err = -EFAULT;
+				opt->optlen = fsize;
 			} else {
 				if (len > fsize)
 					len = fsize;
-				if (copy_to_user(optval, ro->filter, len))
+				if (copy_to_iter(ro->filter, len,
+						 &opt->iter_out) != len)
 					err = -EFAULT;
 			}
 		} else {
@@ -801,7 +800,7 @@ static int raw_getsockopt(struct socket *sock, int level, int optname,
 		release_sock(sk);
 
 		if (!err)
-			err = put_user(len, optlen);
+			opt->optlen = len;
 		return err;
 	}
 	case CAN_RAW_ERR_FILTER:
@@ -845,16 +844,16 @@ static int raw_getsockopt(struct socket *sock, int level, int optname,
 		if (len < sizeof(ro->raw_vcid_opts)) {
 			/* return -ERANGE and needed space in optlen */
 			err = -ERANGE;
-			if (put_user(sizeof(ro->raw_vcid_opts), optlen))
-				err = -EFAULT;
+			opt->optlen = sizeof(ro->raw_vcid_opts);
 		} else {
 			if (len > sizeof(ro->raw_vcid_opts))
 				len = sizeof(ro->raw_vcid_opts);
-			if (copy_to_user(optval, &ro->raw_vcid_opts, len))
+			if (copy_to_iter(&ro->raw_vcid_opts, len,
+					 &opt->iter_out) != len)
 				err = -EFAULT;
 		}
 		if (!err)
-			err = put_user(len, optlen);
+			opt->optlen = len;
 		return err;
 	}
 	case CAN_RAW_JOIN_FILTERS:
@@ -868,9 +867,8 @@ static int raw_getsockopt(struct socket *sock, int level, int optname,
 		return -ENOPROTOOPT;
 	}
 
-	if (put_user(len, optlen))
-		return -EFAULT;
-	if (copy_to_user(optval, val, len))
+	opt->optlen = len;
+	if (copy_to_iter(val, len, &opt->iter_out) != len)
 		return -EFAULT;
 	return 0;
 }
@@ -1077,7 +1075,7 @@ static const struct proto_ops raw_ops = {
 	.listen        = sock_no_listen,
 	.shutdown      = sock_no_shutdown,
 	.setsockopt    = raw_setsockopt,
-	.getsockopt    = raw_getsockopt,
+	.getsockopt_iter = raw_getsockopt,
 	.sendmsg       = raw_sendmsg,
 	.recvmsg       = raw_recvmsg,
 	.mmap          = sock_no_mmap,

-- 
2.52.0


^ permalink raw reply related

* [PATCH net-next v3 2/4] net: call getsockopt_iter if available
From: Breno Leitao @ 2026-04-08 10:30 UTC (permalink / raw)
  To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, Kuniyuki Iwashima, Willem de Bruijn, metze, axboe,
	Stanislav Fomichev
  Cc: io-uring, bpf, netdev, Linus Torvalds, linux-kernel, kernel-team,
	Breno Leitao
In-Reply-To: <20260408-getsockopt-v3-0-061bb9cb355d@debian.org>

Update do_sock_getsockopt() to use the new getsockopt_iter callback
when available. Add do_sock_getsockopt_iter() helper that:

1. Reads optlen from user/kernel space
2. Initializes a sockopt_t with the appropriate iov_iter (kvec for
   kernel, ubuf for user buffers) and sets opt.optlen
3. Calls the protocol's getsockopt_iter callback
4. Writes opt.optlen back to user/kernel space

The optlen is always written back, even on failure. Some protocols
(e.g. CAN raw) return -ERANGE and set optlen to the required buffer
size so userspace knows how much to allocate.

The callback is responsible for setting opt.optlen to indicate the
returned data size.

Important to say that  iov_out does not need to be copied back in
do_sock_getsockopt().

When optval is not kernel (the userspace path), sockptr_to_sockopt()
sets up opt->iter_out as a ITER_DEST ubuf iterator pointing directly at
the userspace buffer (optval.user). So when getsockopt_iter
implementations call copy_to_iter(..., &opt->iter_out), the data is
written directly to userspace — no intermediate kernel buffer is
involved.

When optval.is_kernel is true (the in-kernel path, e.g. from io_uring),
the kvec points at the already-provided kernel buffer (optval.kernel),
so the data lands in the caller's buffer directly via the kvec-backed
iterator.

In both cases the iterator writes to the final destination in-place at
protocol callback. There's nothing to copy back — only optlen needs to
be written back.

Signed-off-by: Breno Leitao <leitao@debian.org>
---
 net/socket.c | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 51 insertions(+), 3 deletions(-)

diff --git a/net/socket.c b/net/socket.c
index ade2ff5845a0c..a25e513cf0f47 100644
--- a/net/socket.c
+++ b/net/socket.c
@@ -77,6 +77,7 @@
 #include <linux/mount.h>
 #include <linux/pseudo_fs.h>
 #include <linux/security.h>
+#include <linux/uio.h>
 #include <linux/syscalls.h>
 #include <linux/compat.h>
 #include <linux/kmod.h>
@@ -2349,11 +2350,45 @@ SYSCALL_DEFINE5(setsockopt, int, fd, int, level, int, optname,
 INDIRECT_CALLABLE_DECLARE(bool tcp_bpf_bypass_getsockopt(int level,
 							 int optname));
 
+/*
+ * Initialize a sockopt_t from sockptr optval/optlen, setting up iov_iter
+ * for both input and output directions.
+ * It is important to remember that both iov points to the same data, but,
+ * .iter_in is read-only and .iter_out is write-only by the protocol callbacks
+ */
+static int sockptr_to_sockopt(sockopt_t *opt, sockptr_t optval,
+			      sockptr_t optlen, struct kvec *kvec)
+{
+	int koptlen;
+
+	if (copy_from_sockptr(&koptlen, optlen, sizeof(int)))
+		return -EFAULT;
+
+	if (koptlen < 0)
+		return -EINVAL;
+
+	if (optval.is_kernel) {
+		kvec->iov_base = optval.kernel;
+		kvec->iov_len = koptlen;
+		iov_iter_kvec(&opt->iter_out, ITER_DEST, kvec, 1, koptlen);
+		iov_iter_kvec(&opt->iter_in, ITER_SOURCE, kvec, 1, koptlen);
+	} else {
+		iov_iter_ubuf(&opt->iter_out, ITER_DEST, optval.user, koptlen);
+		iov_iter_ubuf(&opt->iter_in, ITER_SOURCE, optval.user,
+			      koptlen);
+	}
+	opt->optlen = koptlen;
+
+	return 0;
+}
+
 int do_sock_getsockopt(struct socket *sock, bool compat, int level,
 		       int optname, sockptr_t optval, sockptr_t optlen)
 {
 	int max_optlen __maybe_unused = 0;
 	const struct proto_ops *ops;
+	struct kvec kvec;
+	sockopt_t opt;
 	int err;
 
 	err = security_socket_getsockopt(sock, level, optname);
@@ -2366,15 +2401,28 @@ int do_sock_getsockopt(struct socket *sock, bool compat, int level,
 	ops = READ_ONCE(sock->ops);
 	if (level == SOL_SOCKET) {
 		err = sk_getsockopt(sock->sk, level, optname, optval, optlen);
-	} else if (unlikely(!ops->getsockopt)) {
-		err = -EOPNOTSUPP;
-	} else {
+	} else if (ops->getsockopt_iter) {
+		err = sockptr_to_sockopt(&opt, optval, optlen, &kvec);
+		if (err)
+			return err;
+
+		err = ops->getsockopt_iter(sock, level, optname, &opt);
+
+		/* Always write back optlen, even on failure. Some protocols
+		 * (e.g. CAN raw) return -ERANGE and set optlen to the
+		 * required buffer size so userspace can discover it.
+		 */
+		if (copy_to_sockptr(optlen, &opt.optlen, sizeof(int)))
+			return -EFAULT;
+	} else if (ops->getsockopt) {
 		if (WARN_ONCE(optval.is_kernel || optlen.is_kernel,
 			      "Invalid argument type"))
 			return -EOPNOTSUPP;
 
 		err = ops->getsockopt(sock, level, optname, optval.user,
 				      optlen.user);
+	} else {
+		err = -EOPNOTSUPP;
 	}
 
 	if (!compat)

-- 
2.52.0


^ permalink raw reply related

* [PATCH net-next v3 3/4] af_packet: convert to getsockopt_iter
From: Breno Leitao @ 2026-04-08 10:30 UTC (permalink / raw)
  To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, Kuniyuki Iwashima, Willem de Bruijn, metze, axboe,
	Stanislav Fomichev
  Cc: io-uring, bpf, netdev, Linus Torvalds, linux-kernel, kernel-team,
	Breno Leitao
In-Reply-To: <20260408-getsockopt-v3-0-061bb9cb355d@debian.org>

Convert AF_PACKET's getsockopt implementation to use the new
getsockopt_iter callback with sockopt_t.

Key changes:
- Replace (char __user *optval, int __user *optlen) with sockopt_t *opt
- Use opt->optlen for buffer length (input) and returned size (output)
- Use copy_to_iter() instead of put_user()/copy_to_user()
- For PACKET_HDRLEN which reads from optval: use opt->iter_in with
  copy_from_iter() for the input read, then the common opt->iter_out
  copy_to_iter() epilogue handles the output

Signed-off-by: Breno Leitao <leitao@debian.org>
---
 net/packet/af_packet.c | 15 +++++++--------
 1 file changed, 7 insertions(+), 8 deletions(-)

diff --git a/net/packet/af_packet.c b/net/packet/af_packet.c
index bb2d88205e5a6..1da78b6ad3d5f 100644
--- a/net/packet/af_packet.c
+++ b/net/packet/af_packet.c
@@ -49,6 +49,7 @@
 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
 
 #include <linux/ethtool.h>
+#include <linux/uio.h>
 #include <linux/filter.h>
 #include <linux/types.h>
 #include <linux/mm.h>
@@ -4051,7 +4052,7 @@ packet_setsockopt(struct socket *sock, int level, int optname, sockptr_t optval,
 }
 
 static int packet_getsockopt(struct socket *sock, int level, int optname,
-			     char __user *optval, int __user *optlen)
+			     sockopt_t *opt)
 {
 	int len;
 	int val, lv = sizeof(val);
@@ -4065,8 +4066,7 @@ static int packet_getsockopt(struct socket *sock, int level, int optname,
 	if (level != SOL_PACKET)
 		return -ENOPROTOOPT;
 
-	if (get_user(len, optlen))
-		return -EFAULT;
+	len = opt->optlen;
 
 	if (len < 0)
 		return -EINVAL;
@@ -4115,7 +4115,7 @@ static int packet_getsockopt(struct socket *sock, int level, int optname,
 			len = sizeof(int);
 		if (len < sizeof(int))
 			return -EINVAL;
-		if (copy_from_user(&val, optval, len))
+		if (copy_from_iter(&val, len, &opt->iter_in) != len)
 			return -EFAULT;
 		switch (val) {
 		case TPACKET_V1:
@@ -4171,9 +4171,8 @@ static int packet_getsockopt(struct socket *sock, int level, int optname,
 
 	if (len > lv)
 		len = lv;
-	if (put_user(len, optlen))
-		return -EFAULT;
-	if (copy_to_user(optval, data, len))
+	opt->optlen = len;
+	if (copy_to_iter(data, len, &opt->iter_out) != len)
 		return -EFAULT;
 	return 0;
 }
@@ -4672,7 +4671,7 @@ static const struct proto_ops packet_ops = {
 	.listen =	sock_no_listen,
 	.shutdown =	sock_no_shutdown,
 	.setsockopt =	packet_setsockopt,
-	.getsockopt =	packet_getsockopt,
+	.getsockopt_iter =	packet_getsockopt,
 	.sendmsg =	packet_sendmsg,
 	.recvmsg =	packet_recvmsg,
 	.mmap =		packet_mmap,

-- 
2.52.0


^ permalink raw reply related

* [PATCH net-next v3 0/4] net: move .getsockopt away from __user buffers
From: Breno Leitao @ 2026-04-08 10:30 UTC (permalink / raw)
  To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, Kuniyuki Iwashima, Willem de Bruijn, metze, axboe,
	Stanislav Fomichev
  Cc: io-uring, bpf, netdev, Linus Torvalds, linux-kernel, kernel-team,
	Breno Leitao

Currently, the .getsockopt callback requires __user pointers:

  int (*getsockopt)(struct socket *sock, int level,
                    int optname, char __user *optval, int __user *optlen);

This prevents kernel callers (io_uring, BPF) from using getsockopt on
levels other than SOL_SOCKET, since they pass kernel pointers.

Following Linus' suggestion [0], this series introduces sockopt_t, a
type-safe wrapper around iov_iter, and a getsockopt_iter callback that
works with both user and kernel buffers. AF_PACKET and CAN raw are
converted as initial users, with selftests covering the trickiest
conversion patterns.

[0] https://lore.kernel.org/all/CAHk-=whmzrO-BMU=uSVXbuoLi-3tJsO=0kHj1BCPBE3F2kVhTA@mail.gmail.com/

Updates from v2 to v3:

* Use two iov in sockopt_t instead of a single one:
  a) .iter_in that is populated by the caller and will be read-only in
  the protocols callback.

  b) .iter_out will be populated by the protocol and it will be sent
  back to the caller.

  - This will avoid changing the protocol reset and changing the data
    source at the callback, making the driver callback implementation
    and converstion saner.

* created sockptr_to_sockopt() to convert sockptr to sockopt, making the
  call to getsockopt_iter straight-forward

Link: https://lore.kernel.org/all/CAHk-=whmzrO-BMU=uSVXbuoLi-3tJsO=0kHj1BCPBE3F2kVhTA@mail.gmail.com/ [0]
---
Changes in v3:
- Create Two iov in sockopt_t instead of a single one (Stanislav Fomichev)
- Implement the sockptr_to_sockopt() helper (Stanislav Fomichev)
- Link to v2: https://patch.msgid.link/20260401-getsockopt-v2-0-611df6771aff@debian.org

Changes in v2:
- Restore optlen even on error path (getsockopt_iter fails)
- Move af_packet.c and can instead of netlink (given these are the most
  complicate ones).
- Link to v1: https://patch.msgid.link/20260130-getsockopt-v1-0-9154fcff6f95@debian.org

---
Breno Leitao (4):
      net: add getsockopt_iter callback to proto_ops
      net: call getsockopt_iter if available
      af_packet: convert to getsockopt_iter
      can: raw: convert to getsockopt_iter

 include/linux/net.h    | 23 +++++++++++++++++++++
 net/can/raw.c          | 28 ++++++++++++--------------
 net/packet/af_packet.c | 15 +++++++-------
 net/socket.c           | 54 +++++++++++++++++++++++++++++++++++++++++++++++---
 4 files changed, 94 insertions(+), 26 deletions(-)
---
base-commit: 9c14d60a50c4b726a3613a02e8b74778e9964891
change-id: 20260130-getsockopt-9f36625eedcb

Best regards,
--  
Breno Leitao <leitao@debian.org>


^ permalink raw reply

* [PATCH net-next v3 1/4] net: add getsockopt_iter callback to proto_ops
From: Breno Leitao @ 2026-04-08 10:30 UTC (permalink / raw)
  To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, Kuniyuki Iwashima, Willem de Bruijn, metze, axboe,
	Stanislav Fomichev
  Cc: io-uring, bpf, netdev, Linus Torvalds, linux-kernel, kernel-team,
	Breno Leitao
In-Reply-To: <20260408-getsockopt-v3-0-061bb9cb355d@debian.org>

Add a new getsockopt_iter callback to struct proto_ops that uses
sockopt_t, a type-safe wrapper around iov_iter. This provides a clean
interface for socket option operations that works with both user and
kernel buffers.

The sockopt_t type encapsulates an iov_iter and an optlen field.

The optlen field, although not suggested by Linus, serves as both input
(buffer size) and output (returned data size), allowing callbacks to
return random values independent of the bytes written via
copy_to_iter(), so, keep it separated from iov_iter.count.

This is preparatory work for removing the SOL_SOCKET level restriction
from io_uring getsockopt operations.

Keep in mind that both iter_out and iter_in always point to the same
data at all times, and we just have two of them to make the callback
implementation sane.

Suggested-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Breno Leitao <leitao@debian.org>
---
 include/linux/net.h | 23 +++++++++++++++++++++++
 1 file changed, 23 insertions(+)

diff --git a/include/linux/net.h b/include/linux/net.h
index a8e818de95b33..fdd48d5c94441 100644
--- a/include/linux/net.h
+++ b/include/linux/net.h
@@ -23,9 +23,30 @@
 #include <linux/fs.h>
 #include <linux/mm.h>
 #include <linux/sockptr.h>
+#include <linux/uio.h>
 
 #include <uapi/linux/net.h>
 
+/**
+ * struct sockopt - socket option value container
+ * @iter_in: iov_iter for reading optval with the content from the caller.
+ *	     Use copy_from_iter() given this iov direction is ITER_SOURCE
+ * @iter_out: iov_iter for protocols to update optval data to userspace
+ *	      Use _copy_to_iter() given iov direction is ITER_DEST
+ * @optlen: serves as both input (buffer size) and output (returned data size).
+ *
+ * Type-safe wrapper for socket option data that works with both
+ * user and kernel buffers.
+ *
+ * The optlen field allows callbacks to return a specific length value
+ * independent of the bytes written via copy_to_iter().
+ */
+typedef struct sockopt {
+	struct iov_iter iter_in;
+	struct iov_iter iter_out;
+	int optlen;
+} sockopt_t;
+
 struct poll_table_struct;
 struct pipe_inode_info;
 struct inode;
@@ -192,6 +213,8 @@ struct proto_ops {
 				      unsigned int optlen);
 	int		(*getsockopt)(struct socket *sock, int level,
 				      int optname, char __user *optval, int __user *optlen);
+	int		(*getsockopt_iter)(struct socket *sock, int level,
+					   int optname, sockopt_t *opt);
 	void		(*show_fdinfo)(struct seq_file *m, struct socket *sock);
 	int		(*sendmsg)   (struct socket *sock, struct msghdr *m,
 				      size_t total_len);

-- 
2.52.0


^ permalink raw reply related

* [PATCH net-next v3 5/5] dpll: zl3073x: add ref-sync pair support
From: Ivan Vecera @ 2026-04-08 10:27 UTC (permalink / raw)
  To: netdev
  Cc: Petr Oros, Prathosh Satish, Arkadiusz Kubalewski, Jiri Pirko,
	Michal Schmidt, Simon Horman, Vadim Fedorenko, linux-kernel,
	Conor Dooley, Krzysztof Kozlowski, Rob Herring, devicetree,
	Pasi Vaananen
In-Reply-To: <20260408102716.443099-1-ivecera@redhat.com>

Add support for ref-sync pair registration using the 'ref-sync-sources'
phandle property from device tree. A ref-sync pair consists of a clock
reference and a low-frequency sync signal where the DPLL locks to the
clock reference but phase-aligns to the sync reference.

The implementation:
- Stores fwnode handle in zl3073x_dpll_pin during pin registration
- Adds ref_sync_get/set callbacks to read and write the sync control
  mode and pair registers
- Validates ref-sync frequency constraints: sync signal must be 8 kHz
  or less, clock reference must be 1 kHz or more and higher than sync
- Excludes sync source from automatic reference selection by setting
  its priority to NONE on connect; on disconnect the priority is left
  as NONE and the user must explicitly make the pin selectable again
- Iterates ref-sync-sources phandles to register declared pairings
  via dpll_pin_ref_sync_pair_add()

Reviewed-by: Petr Oros <poros@redhat.com>
Reviewed-by: Prathosh Satish <Prathosh.Satish@microchip.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
 drivers/dpll/zl3073x/dpll.c | 207 +++++++++++++++++++++++++++++++++++-
 1 file changed, 206 insertions(+), 1 deletion(-)

diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
index dc649cf103cb4..c95e93ef3ab04 100644
--- a/drivers/dpll/zl3073x/dpll.c
+++ b/drivers/dpll/zl3073x/dpll.c
@@ -13,6 +13,7 @@
 #include <linux/module.h>
 #include <linux/netlink.h>
 #include <linux/platform_device.h>
+#include <linux/property.h>
 #include <linux/slab.h>
 #include <linux/sprintf.h>
 
@@ -30,6 +31,7 @@
  * @dpll: DPLL the pin is registered to
  * @dpll_pin: pointer to registered dpll_pin
  * @tracker: tracking object for the acquired reference
+ * @fwnode: firmware node handle
  * @label: package label
  * @dir: pin direction
  * @id: pin id
@@ -46,6 +48,7 @@ struct zl3073x_dpll_pin {
 	struct zl3073x_dpll	*dpll;
 	struct dpll_pin		*dpll_pin;
 	dpll_tracker		tracker;
+	struct fwnode_handle	*fwnode;
 	char			label[8];
 	enum dpll_pin_direction	dir;
 	u8			id;
@@ -186,6 +189,109 @@ zl3073x_dpll_input_pin_esync_set(const struct dpll_pin *dpll_pin,
 	return zl3073x_ref_state_set(zldev, ref_id, &ref);
 }
 
+static int
+zl3073x_dpll_input_pin_ref_sync_get(const struct dpll_pin *dpll_pin,
+				    void *pin_priv,
+				    const struct dpll_pin *ref_sync_pin,
+				    void *ref_sync_pin_priv,
+				    enum dpll_pin_state *state,
+				    struct netlink_ext_ack *extack)
+{
+	struct zl3073x_dpll_pin *sync_pin = ref_sync_pin_priv;
+	struct zl3073x_dpll_pin *pin = pin_priv;
+	struct zl3073x_dpll *zldpll = pin->dpll;
+	struct zl3073x_dev *zldev = zldpll->dev;
+	const struct zl3073x_ref *ref;
+	u8 ref_id, mode, pair;
+
+	ref_id = zl3073x_input_pin_ref_get(pin->id);
+	ref = zl3073x_ref_state_get(zldev, ref_id);
+	mode = zl3073x_ref_sync_mode_get(ref);
+	pair = zl3073x_ref_sync_pair_get(ref);
+
+	if (mode == ZL_REF_SYNC_CTRL_MODE_REFSYNC_PAIR &&
+	    pair == zl3073x_input_pin_ref_get(sync_pin->id))
+		*state = DPLL_PIN_STATE_CONNECTED;
+	else
+		*state = DPLL_PIN_STATE_DISCONNECTED;
+
+	return 0;
+}
+
+static int
+zl3073x_dpll_input_pin_ref_sync_set(const struct dpll_pin *dpll_pin,
+				    void *pin_priv,
+				    const struct dpll_pin *ref_sync_pin,
+				    void *ref_sync_pin_priv,
+				    const enum dpll_pin_state state,
+				    struct netlink_ext_ack *extack)
+{
+	struct zl3073x_dpll_pin *sync_pin = ref_sync_pin_priv;
+	struct zl3073x_dpll_pin *pin = pin_priv;
+	struct zl3073x_dpll *zldpll = pin->dpll;
+	struct zl3073x_dev *zldev = zldpll->dev;
+	u8 mode, ref_id, sync_ref_id;
+	struct zl3073x_chan chan;
+	struct zl3073x_ref ref;
+	int rc;
+
+	ref_id = zl3073x_input_pin_ref_get(pin->id);
+	sync_ref_id = zl3073x_input_pin_ref_get(sync_pin->id);
+	ref = *zl3073x_ref_state_get(zldev, ref_id);
+
+	if (state == DPLL_PIN_STATE_CONNECTED) {
+		const struct zl3073x_ref *sync_ref;
+		u32 ref_freq, sync_freq;
+
+		sync_ref = zl3073x_ref_state_get(zldev, sync_ref_id);
+		ref_freq = zl3073x_ref_freq_get(&ref);
+		sync_freq = zl3073x_ref_freq_get(sync_ref);
+
+		/* Sync signal must be 8 kHz or less and clock reference
+		 * must be 1 kHz or more and higher than the sync signal.
+		 */
+		if (sync_freq > 8000) {
+			NL_SET_ERR_MSG(extack,
+				       "sync frequency must be 8 kHz or less");
+			return -EINVAL;
+		}
+		if (ref_freq < 1000) {
+			NL_SET_ERR_MSG(extack,
+				       "clock frequency must be 1 kHz or more");
+			return -EINVAL;
+		}
+		if (ref_freq <= sync_freq) {
+			NL_SET_ERR_MSG(extack,
+				       "clock frequency must be higher than sync frequency");
+			return -EINVAL;
+		}
+
+		zl3073x_ref_sync_pair_set(&ref, sync_ref_id);
+		mode = ZL_REF_SYNC_CTRL_MODE_REFSYNC_PAIR;
+	} else {
+		mode = ZL_REF_SYNC_CTRL_MODE_REFSYNC_PAIR_OFF;
+	}
+
+	zl3073x_ref_sync_mode_set(&ref, mode);
+
+	rc = zl3073x_ref_state_set(zldev, ref_id, &ref);
+	if (rc)
+		return rc;
+
+	/* Exclude sync source from automatic reference selection by setting
+	 * its priority to NONE. On disconnect the priority is left as NONE
+	 * and the user must explicitly make the pin selectable again.
+	 */
+	if (state == DPLL_PIN_STATE_CONNECTED) {
+		chan = *zl3073x_chan_state_get(zldev, zldpll->id);
+		zl3073x_chan_ref_prio_set(&chan, sync_ref_id,
+					  ZL_DPLL_REF_PRIO_NONE);
+		return zl3073x_chan_state_set(zldev, zldpll->id, &chan);
+	}
+
+	return 0;
+}
+
 static int
 zl3073x_dpll_input_pin_ffo_get(const struct dpll_pin *dpll_pin, void *pin_priv,
 			       const struct dpll_device *dpll, void *dpll_priv,
@@ -1147,6 +1253,8 @@ static const struct dpll_pin_ops zl3073x_dpll_input_pin_ops = {
 	.phase_adjust_set = zl3073x_dpll_input_pin_phase_adjust_set,
 	.prio_get = zl3073x_dpll_input_pin_prio_get,
 	.prio_set = zl3073x_dpll_input_pin_prio_set,
+	.ref_sync_get = zl3073x_dpll_input_pin_ref_sync_get,
+	.ref_sync_set = zl3073x_dpll_input_pin_ref_sync_set,
 	.state_on_dpll_get = zl3073x_dpll_input_pin_state_on_dpll_get,
 	.state_on_dpll_set = zl3073x_dpll_input_pin_state_on_dpll_set,
 };
@@ -1239,8 +1347,11 @@ zl3073x_dpll_pin_register(struct zl3073x_dpll_pin *pin, u32 index)
 	if (IS_ERR(props))
 		return PTR_ERR(props);
 
-	/* Save package label, esync capability and phase adjust granularity */
+	/* Save package label, fwnode, esync capability and phase adjust
+	 * granularity.
+	 */
 	strscpy(pin->label, props->package_label);
+	pin->fwnode = fwnode_handle_get(props->fwnode);
 	pin->esync_control = props->esync_control;
 	pin->phase_gran = props->dpll_props.phase_gran;
 
@@ -1285,6 +1396,8 @@ zl3073x_dpll_pin_register(struct zl3073x_dpll_pin *pin, u32 index)
 	dpll_pin_put(pin->dpll_pin, &pin->tracker);
 	pin->dpll_pin = NULL;
 err_pin_get:
+	fwnode_handle_put(pin->fwnode);
+	pin->fwnode = NULL;
 	zl3073x_pin_props_put(props);
 
 	return rc;
@@ -1314,6 +1427,9 @@ zl3073x_dpll_pin_unregister(struct zl3073x_dpll_pin *pin)
 
 	dpll_pin_put(pin->dpll_pin, &pin->tracker);
 	pin->dpll_pin = NULL;
+
+	fwnode_handle_put(pin->fwnode);
+	pin->fwnode = NULL;
 }
 
 /**
@@ -1827,6 +1943,88 @@ zl3073x_dpll_free(struct zl3073x_dpll *zldpll)
 	kfree(zldpll);
 }
 
+/**
+ * zl3073x_dpll_ref_sync_pair_register - register ref_sync pairs for a pin
+ * @pin: pointer to zl3073x_dpll_pin structure
+ *
+ * Iterates 'ref-sync-sources' phandles in the pin's firmware node and
+ * registers each declared pairing.
+ *
+ * Return: 0 on success, <0 on error
+ */
+static int
+zl3073x_dpll_ref_sync_pair_register(struct zl3073x_dpll_pin *pin)
+{
+	struct zl3073x_dev *zldev = pin->dpll->dev;
+	struct fwnode_handle *fwnode;
+	struct dpll_pin *sync_pin;
+	dpll_tracker tracker;
+	int n, rc;
+
+	for (n = 0; ; n++) {
+		/* Get n'th ref-sync source */
+		fwnode = fwnode_find_reference(pin->fwnode, "ref-sync-sources",
+					       n);
+		if (IS_ERR(fwnode)) {
+			rc = PTR_ERR(fwnode);
+			break;
+		}
+
+		/* Find associated dpll pin */
+		sync_pin = fwnode_dpll_pin_find(fwnode, &tracker);
+		fwnode_handle_put(fwnode);
+		if (!sync_pin) {
+			dev_warn(zldev->dev, "%s: ref-sync source %d not found",
+				 pin->label, n);
+			continue;
+		}
+
+		/* Register new ref-sync pair */
+		rc = dpll_pin_ref_sync_pair_add(pin->dpll_pin, sync_pin);
+		dpll_pin_put(sync_pin, &tracker);
+
+		/* -EBUSY means pairing already exists from another DPLL's
+		 * registration.
+		 */
+		if (rc && rc != -EBUSY) {
+			dev_err(zldev->dev,
+				"%s: failed to add ref-sync source %d: %pe",
+				pin->label, n, ERR_PTR(rc));
+			break;
+		}
+	}
+
+	return rc != -ENOENT ? rc : 0;
+}
+
+/**
+ * zl3073x_dpll_ref_sync_pairs_register - register ref_sync pairs for a DPLL
+ * @zldpll: pointer to zl3073x_dpll structure
+ *
+ * Iterates all registered input pins of the given DPLL and establishes
+ * ref_sync pairings declared by 'ref-sync-sources' phandles in the
+ * device tree.
+ *
+ * Return: 0 on success, <0 on error
+ */
+static int
+zl3073x_dpll_ref_sync_pairs_register(struct zl3073x_dpll *zldpll)
+{
+	struct zl3073x_dpll_pin *pin;
+	int rc;
+
+	list_for_each_entry(pin, &zldpll->pins, list) {
+		if (!zl3073x_dpll_is_input_pin(pin) || !pin->fwnode)
+			continue;
+
+		rc = zl3073x_dpll_ref_sync_pair_register(pin);
+		if (rc)
+			return rc;
+	}
+
+	return 0;
+}
+
 /**
  * zl3073x_dpll_register - register DPLL device and all its pins
  * @zldpll: pointer to zl3073x_dpll structure
@@ -1850,6 +2048,13 @@ zl3073x_dpll_register(struct zl3073x_dpll *zldpll)
 		return rc;
 	}
 
+	rc = zl3073x_dpll_ref_sync_pairs_register(zldpll);
+	if (rc) {
+		zl3073x_dpll_pins_unregister(zldpll);
+		zl3073x_dpll_device_unregister(zldpll);
+		return rc;
+	}
+
 	return 0;
 }
 
-- 
2.52.0


^ permalink raw reply related


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