Netdev List
 help / color / mirror / Atom feed
* [PATCH net v4 2/2] amt: make the head writable before rewriting the L2 header
From: Michael Bommarito @ 2026-07-07 19:32 UTC (permalink / raw)
  To: Taehee Yoo, Andrew Lunn, David S . Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni
  Cc: netdev, linux-kernel
In-Reply-To: <20260707193243.3448201-1-michael.bommarito@gmail.com>

amt_multicast_data_handler(), amt_membership_query_handler() and
amt_update_handler() rewrite the ethernet header of the decapsulated
skb in place (eth->h_proto, eth->h_dest and, for the query, also
eth->h_source) before handing it up the stack.  The skb head may be
shared, for example when a packet tap has cloned it on the underlay
interface, so writing through it corrupts the other reader's copy.

Call skb_cow_head() before the rewrite so the head is private.  It is
placed before the pointers into the head are (re-)derived, so a
reallocation caused by the copy is picked up by those derivations.

Fixes: cbc21dc1cfe9 ("amt: add data plane of amt interface")
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
---
 drivers/net/amt.c | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/drivers/net/amt.c b/drivers/net/amt.c
index 1d6b2de4b73cc..f7375e40bc8f2 100644
--- a/drivers/net/amt.c
+++ b/drivers/net/amt.c
@@ -2330,6 +2330,12 @@ static bool amt_multicast_data_handler(struct amt_dev *amt, struct sk_buff *skb)
 	skb_reset_mac_header(skb);
 	skb_pull(skb, sizeof(*eth));
 
+	/* The L2 header is rewritten below; make the head private first so a
+	 * cloned skb (for example one taken by a packet tap) is not modified.
+	 */
+	if (skb_cow_head(skb, 0))
+		return true;
+
 	if (!pskb_may_pull(skb, sizeof(*iph)))
 		return true;
 	iph = ip_hdr(skb);
@@ -2413,6 +2419,11 @@ static bool amt_membership_query_handler(struct amt_dev *amt,
 	eth = eth_hdr(skb);
 	/* Snapshot the outer source MAC before a later pull can free it. */
 	ether_addr_copy(h_source, oeth->h_source);
+	/* The L2 header is rewritten below; make the head private first so a
+	 * cloned skb (for example one taken by a packet tap) is not modified.
+	 */
+	if (skb_cow_head(skb, 0))
+		return true;
 	if (!pskb_may_pull(skb, sizeof(*iph)))
 		return true;
 
@@ -2538,6 +2549,12 @@ static bool amt_update_handler(struct amt_dev *amt, struct sk_buff *skb)
 	if (!pskb_may_pull(skb, sizeof(*iph)))
 		return true;
 
+	/* The L2 header is rewritten below; make the head private first so a
+	 * cloned skb (for example one taken by a packet tap) is not modified.
+	 */
+	if (skb_cow_head(skb, 0))
+		return true;
+
 	iph = ip_hdr(skb);
 	if (iph->version == 4) {
 		if (ip_mc_check_igmp(skb)) {
-- 
2.53.0


^ permalink raw reply related

* [PATCH net v4 1/2] amt: re-read skb header pointers after every pull
From: Michael Bommarito @ 2026-07-07 19:32 UTC (permalink / raw)
  To: Taehee Yoo, Andrew Lunn, David S . Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni
  Cc: netdev, linux-kernel
In-Reply-To: <20260707193243.3448201-1-michael.bommarito@gmail.com>

Several AMT receive and transmit paths cache a pointer into the skb
header (ip_hdr(), ipv6_hdr(), eth_hdr() or the AMT message header) and
then call a helper that can reallocate the skb head before the cached
pointer is used again.  pskb_may_pull(), ip_mc_may_pull(),
ipv6_mc_may_pull(), iptunnel_pull_header(), ip_mc_check_igmp() and
ipv6_mc_check_mld() can all free the old head and move the data, so a
pointer taken before the call dangles afterwards and the later access
is a use-after-free of the freed head.

The affected sites are:

  amt_rcv() caches ip_hdr() before amt_parse_type() pulls, then reads
  iph->saddr.

  amt_dev_xmit() caches ip_hdr()/ipv6_hdr() before ip_mc_check_igmp()/
  ipv6_mc_check_mld() and pskb_may_pull(), then reads the group
  address.

  amt_multicast_data_handler() caches eth_hdr() before pskb_may_pull(),
  then writes the L2 header.

  amt_membership_query_handler() caches the AMT header, the outer and
  inner eth_hdr() and ip_hdr() before several pulls, then reads and
  writes them.

  amt_igmpv3_report_handler() and amt_mldv2_report_handler() cache
  ip_hdr()/ipv6_hdr() and the current group record and read the record
  count from the report header inside the record loop, across the
  *_mc_may_pull() calls.

  amt_update_handler() caches ip_hdr() before pskb_may_pull(),
  iptunnel_pull_header(), ip_mc_check_igmp() and the report handler,
  then reads iph->daddr.

Fix each site by either snapshotting the scalar that is used after the
pull before the first pull runs, or re-deriving the header pointer from
the skb after the last pull that can move the head.  Values that are
stable across the pull (source and group address, the response MAC, the
record count, the outer source MAC) are snapshotted; pointers that are
written through or read repeatedly are re-derived.

An earlier fix snapshotted only the source address in a subset of the
handlers and described amt_membership_query_handler() and
amt_multicast_data_handler() as unaffected.  That was wrong: both
re-derive ip_hdr() once but keep stale eth_hdr() and AMT-header
pointers across later pulls, so they are covered here as well.

Fixes: cbc21dc1cfe9 ("amt: add data plane of amt interface")
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
---
 drivers/net/amt.c | 100 ++++++++++++++++++++++++++++++++++++----------
 1 file changed, 79 insertions(+), 21 deletions(-)

diff --git a/drivers/net/amt.c b/drivers/net/amt.c
index f2f3139e38a59..1d6b2de4b73cc 100644
--- a/drivers/net/amt.c
+++ b/drivers/net/amt.c
@@ -1211,7 +1211,7 @@ static netdev_tx_t amt_dev_xmit(struct sk_buff *skb, struct net_device *dev)
 			data = true;
 		}
 		v6 = false;
-		group.ip4 = iph->daddr;
+		group.ip4 = ip_hdr(skb)->daddr;
 #if IS_ENABLED(CONFIG_IPV6)
 	} else if (iph->version == 6) {
 		ip6h = ipv6_hdr(skb);
@@ -1235,7 +1235,7 @@ static netdev_tx_t amt_dev_xmit(struct sk_buff *skb, struct net_device *dev)
 			data = true;
 		}
 		v6 = true;
-		group.ip6 = ip6h->daddr;
+		group.ip6 = ipv6_hdr(skb)->daddr;
 #endif
 	} else {
 		dev->stats.tx_errors++;
@@ -1278,12 +1278,12 @@ static netdev_tx_t amt_dev_xmit(struct sk_buff *skb, struct net_device *dev)
 			hlist_for_each_entry_rcu(gnode, &tunnel->groups[hash],
 						 node) {
 				if (!v6) {
-					if (gnode->group_addr.ip4 == iph->daddr)
+					if (gnode->group_addr.ip4 == group.ip4)
 						goto found;
 #if IS_ENABLED(CONFIG_IPV6)
 				} else {
 					if (ipv6_addr_equal(&gnode->group_addr.ip6,
-							    &ip6h->daddr))
+							    &group.ip6))
 						goto found;
 #endif
 				}
@@ -2000,14 +2000,22 @@ static void amt_igmpv3_report_handler(struct amt_dev *amt, struct sk_buff *skb,
 	struct igmpv3_report *ihrv3 = igmpv3_report_hdr(skb);
 	int len = skb_transport_offset(skb) + sizeof(*ihrv3);
 	void *zero_grec = (void *)&igmpv3_zero_grec;
-	struct iphdr *iph = ip_hdr(skb);
 	struct amt_group_node *gnode;
 	union amt_addr group, host;
 	struct igmpv3_grec *grec;
+	__be32 saddr;
 	u16 nsrcs;
+	u16 ngrec;
 	int i;
 
-	for (i = 0; i < ntohs(ihrv3->ngrec); i++) {
+	/* ihrv3 and ip_hdr() point into the skb head, which the
+	 * ip_mc_may_pull() calls below can reallocate.  Read every field
+	 * that is used after a pull before the first one runs.
+	 */
+	saddr = ip_hdr(skb)->saddr;
+	ngrec = ntohs(ihrv3->ngrec);
+
+	for (i = 0; i < ngrec; i++) {
 		len += sizeof(*grec);
 		if (!ip_mc_may_pull(skb, len))
 			break;
@@ -2019,10 +2027,14 @@ static void amt_igmpv3_report_handler(struct amt_dev *amt, struct sk_buff *skb,
 		if (!ip_mc_may_pull(skb, len))
 			break;
 
+		/* ip_mc_may_pull() may have reallocated the head. */
+		grec = (void *)(skb->data + len - sizeof(*grec) -
+				nsrcs * sizeof(__be32));
+
 		memset(&group, 0, sizeof(union amt_addr));
 		group.ip4 = grec->grec_mca;
 		memset(&host, 0, sizeof(union amt_addr));
-		host.ip4 = iph->saddr;
+		host.ip4 = saddr;
 		gnode = amt_lookup_group(tunnel, &group, &host, false);
 		if (!gnode) {
 			gnode = amt_add_group(amt, tunnel, &group, &host,
@@ -2162,14 +2174,22 @@ static void amt_mldv2_report_handler(struct amt_dev *amt, struct sk_buff *skb,
 	struct mld2_report *mld2r = (struct mld2_report *)icmp6_hdr(skb);
 	int len = skb_transport_offset(skb) + sizeof(*mld2r);
 	void *zero_grec = (void *)&mldv2_zero_grec;
-	struct ipv6hdr *ip6h = ipv6_hdr(skb);
 	struct amt_group_node *gnode;
 	union amt_addr group, host;
 	struct mld2_grec *grec;
+	struct in6_addr saddr;
 	u16 nsrcs;
+	u16 ngrec;
 	int i;
 
-	for (i = 0; i < ntohs(mld2r->mld2r_ngrec); i++) {
+	/* mld2r and ipv6_hdr() point into the skb head, which the
+	 * ipv6_mc_may_pull() calls below can reallocate.  Read every field
+	 * that is used after a pull before the first one runs.
+	 */
+	saddr = ipv6_hdr(skb)->saddr;
+	ngrec = ntohs(mld2r->mld2r_ngrec);
+
+	for (i = 0; i < ngrec; i++) {
 		len += sizeof(*grec);
 		if (!ipv6_mc_may_pull(skb, len))
 			break;
@@ -2181,10 +2201,14 @@ static void amt_mldv2_report_handler(struct amt_dev *amt, struct sk_buff *skb,
 		if (!ipv6_mc_may_pull(skb, len))
 			break;
 
+		/* ipv6_mc_may_pull() may have reallocated the head. */
+		grec = (void *)(skb->data + len - sizeof(*grec) -
+				nsrcs * sizeof(struct in6_addr));
+
 		memset(&group, 0, sizeof(union amt_addr));
 		group.ip6 = grec->grec_mca;
 		memset(&host, 0, sizeof(union amt_addr));
-		host.ip6 = ip6h->saddr;
+		host.ip6 = saddr;
 		gnode = amt_lookup_group(tunnel, &group, &host, true);
 		if (!gnode) {
 			gnode = amt_add_group(amt, tunnel, &group, &host,
@@ -2305,7 +2329,6 @@ static bool amt_multicast_data_handler(struct amt_dev *amt, struct sk_buff *skb)
 	skb_push(skb, sizeof(*eth));
 	skb_reset_mac_header(skb);
 	skb_pull(skb, sizeof(*eth));
-	eth = eth_hdr(skb);
 
 	if (!pskb_may_pull(skb, sizeof(*iph)))
 		return true;
@@ -2315,6 +2338,8 @@ static bool amt_multicast_data_handler(struct amt_dev *amt, struct sk_buff *skb)
 		if (!ipv4_is_multicast(iph->daddr))
 			return true;
 		skb->protocol = htons(ETH_P_IP);
+		/* pskb_may_pull() above may have reallocated the head. */
+		eth = eth_hdr(skb);
 		eth->h_proto = htons(ETH_P_IP);
 		ip_eth_mc_map(iph->daddr, eth->h_dest);
 #if IS_ENABLED(CONFIG_IPV6)
@@ -2328,6 +2353,8 @@ static bool amt_multicast_data_handler(struct amt_dev *amt, struct sk_buff *skb)
 		if (!ipv6_addr_is_multicast(&ip6h->daddr))
 			return true;
 		skb->protocol = htons(ETH_P_IPV6);
+		/* pskb_may_pull() above may have reallocated the head. */
+		eth = eth_hdr(skb);
 		eth->h_proto = htons(ETH_P_IPV6);
 		ipv6_eth_mc_map(&ip6h->daddr, eth->h_dest);
 #endif
@@ -2353,8 +2380,10 @@ static bool amt_membership_query_handler(struct amt_dev *amt,
 	struct amt_header_membership_query *amtmq;
 	struct igmpv3_query *ihv3;
 	struct ethhdr *eth, *oeth;
+	u8 h_source[ETH_ALEN];
 	struct iphdr *iph;
 	int hdr_size, len;
+	u64 response_mac;
 
 	hdr_size = sizeof(*amtmq) + sizeof(struct udphdr);
 	if (!pskb_may_pull(skb, hdr_size))
@@ -2367,6 +2396,12 @@ static bool amt_membership_query_handler(struct amt_dev *amt,
 	if (amtmq->nonce != amt->nonce)
 		return true;
 
+	/* iptunnel_pull_header() and the pskb_may_pull() calls below can
+	 * reallocate the skb head, so amtmq would dangle.  Snapshot the only
+	 * field used afterwards.
+	 */
+	response_mac = amtmq->response_mac;
+
 	hdr_size -= sizeof(*eth);
 	if (iptunnel_pull_header(skb, hdr_size, htons(ETH_P_TEB), false))
 		return true;
@@ -2376,6 +2411,8 @@ static bool amt_membership_query_handler(struct amt_dev *amt,
 	skb_pull(skb, sizeof(*eth));
 	skb_reset_network_header(skb);
 	eth = eth_hdr(skb);
+	/* Snapshot the outer source MAC before a later pull can free it. */
+	ether_addr_copy(h_source, oeth->h_source);
 	if (!pskb_may_pull(skb, sizeof(*iph)))
 		return true;
 
@@ -2388,6 +2425,8 @@ static bool amt_membership_query_handler(struct amt_dev *amt,
 				   sizeof(*ihv3)))
 			return true;
 
+		/* pskb_may_pull() above may have reallocated the head. */
+		iph = ip_hdr(skb);
 		if (!ipv4_is_multicast(iph->daddr))
 			return true;
 
@@ -2395,10 +2434,11 @@ static bool amt_membership_query_handler(struct amt_dev *amt,
 		skb_reset_transport_header(skb);
 		skb_push(skb, sizeof(*iph) + AMT_IPHDR_OPTS);
 		WRITE_ONCE(amt->ready4, true);
-		amt->mac = amtmq->response_mac;
+		amt->mac = response_mac;
 		amt->req_cnt = 0;
 		amt->qi = ihv3->qqic;
 		skb->protocol = htons(ETH_P_IP);
+		eth = eth_hdr(skb);
 		eth->h_proto = htons(ETH_P_IP);
 		ip_eth_mc_map(iph->daddr, eth->h_dest);
 #if IS_ENABLED(CONFIG_IPV6)
@@ -2421,10 +2461,11 @@ static bool amt_membership_query_handler(struct amt_dev *amt,
 		skb_reset_transport_header(skb);
 		skb_push(skb, sizeof(*ip6h) + AMT_IP6HDR_OPTS);
 		WRITE_ONCE(amt->ready6, true);
-		amt->mac = amtmq->response_mac;
+		amt->mac = response_mac;
 		amt->req_cnt = 0;
 		amt->qi = mld2q->mld2q_qqic;
 		skb->protocol = htons(ETH_P_IPV6);
+		eth = eth_hdr(skb);
 		eth->h_proto = htons(ETH_P_IPV6);
 		ipv6_eth_mc_map(&ip6h->daddr, eth->h_dest);
 #endif
@@ -2432,7 +2473,7 @@ static bool amt_membership_query_handler(struct amt_dev *amt,
 		return true;
 	}
 
-	ether_addr_copy(eth->h_source, oeth->h_source);
+	ether_addr_copy(eth->h_source, h_source);
 	skb->pkt_type = PACKET_MULTICAST;
 	skb->ip_summed = CHECKSUM_NONE;
 	len = skb->len;
@@ -2455,8 +2496,13 @@ static bool amt_update_handler(struct amt_dev *amt, struct sk_buff *skb)
 	struct ethhdr *eth;
 	struct iphdr *iph;
 	int len, hdr_size;
+	__be32 saddr;
 
-	iph = ip_hdr(skb);
+	/* ip_hdr() points into the skb head, which the pskb_may_pull() /
+	 * iptunnel_pull_header() calls below can reallocate.  Snapshot the
+	 * source address used for the tunnel lookup before the first pull.
+	 */
+	saddr = ip_hdr(skb)->saddr;
 
 	hdr_size = sizeof(*amtmu) + sizeof(struct udphdr);
 	if (!pskb_may_pull(skb, hdr_size))
@@ -2472,7 +2518,7 @@ static bool amt_update_handler(struct amt_dev *amt, struct sk_buff *skb)
 	skb_reset_network_header(skb);
 
 	list_for_each_entry_rcu(tunnel, &amt->tunnel_list, list) {
-		if (tunnel->ip4 == iph->saddr) {
+		if (tunnel->ip4 == saddr) {
 			if ((amtmu->nonce == tunnel->nonce &&
 			     amtmu->response_mac == tunnel->mac)) {
 				mod_delayed_work(amt_wq, &tunnel->gc_wq,
@@ -2508,6 +2554,10 @@ static bool amt_update_handler(struct amt_dev *amt, struct sk_buff *skb)
 		eth = eth_hdr(skb);
 		skb->protocol = htons(ETH_P_IP);
 		eth->h_proto = htons(ETH_P_IP);
+		/* ip_mc_check_igmp() and the report handler may have
+		 * reallocated the head; re-derive iph.
+		 */
+		iph = ip_hdr(skb);
 		ip_eth_mc_map(iph->daddr, eth->h_dest);
 #if IS_ENABLED(CONFIG_IPV6)
 	} else if (iph->version == 6) {
@@ -2527,6 +2577,10 @@ static bool amt_update_handler(struct amt_dev *amt, struct sk_buff *skb)
 		eth = eth_hdr(skb);
 		skb->protocol = htons(ETH_P_IPV6);
 		eth->h_proto = htons(ETH_P_IPV6);
+		/* ipv6_mc_check_mld() and the report handler may have
+		 * reallocated the head; re-derive ip6h.
+		 */
+		ip6h = ipv6_hdr(skb);
 		ipv6_eth_mc_map(&ip6h->daddr, eth->h_dest);
 #endif
 	} else {
@@ -2772,7 +2826,7 @@ static void amt_gw_rcv(struct amt_dev *amt, struct sk_buff *skb)
 static int amt_rcv(struct sock *sk, struct sk_buff *skb)
 {
 	struct amt_dev *amt;
-	struct iphdr *iph;
+	__be32 saddr;
 	int type;
 	bool err;
 
@@ -2785,7 +2839,11 @@ static int amt_rcv(struct sock *sk, struct sk_buff *skb)
 	}
 
 	skb->dev = amt->dev;
-	iph = ip_hdr(skb);
+	/* amt_parse_type() calls pskb_may_pull(), which can reallocate the
+	 * skb head and leave a cached ip_hdr() dangling.  Snapshot the source
+	 * address used below before the pull.
+	 */
+	saddr = ip_hdr(skb)->saddr;
 	type = amt_parse_type(skb);
 	if (type == -1) {
 		err = true;
@@ -2795,7 +2853,7 @@ static int amt_rcv(struct sock *sk, struct sk_buff *skb)
 	if (amt->mode == AMT_MODE_GATEWAY) {
 		switch (type) {
 		case AMT_MSG_ADVERTISEMENT:
-			if (iph->saddr != amt->discovery_ip) {
+			if (saddr != amt->discovery_ip) {
 				netdev_dbg(amt->dev, "Invalid Relay IP\n");
 				err = true;
 				goto drop;
@@ -2807,7 +2865,7 @@ static int amt_rcv(struct sock *sk, struct sk_buff *skb)
 			}
 			goto out;
 		case AMT_MSG_MULTICAST_DATA:
-			if (iph->saddr != amt->remote_ip) {
+			if (saddr != amt->remote_ip) {
 				netdev_dbg(amt->dev, "Invalid Relay IP\n");
 				err = true;
 				goto drop;
@@ -2818,7 +2876,7 @@ static int amt_rcv(struct sock *sk, struct sk_buff *skb)
 			else
 				goto out;
 		case AMT_MSG_MEMBERSHIP_QUERY:
-			if (iph->saddr != amt->remote_ip) {
+			if (saddr != amt->remote_ip) {
 				netdev_dbg(amt->dev, "Invalid Relay IP\n");
 				err = true;
 				goto drop;
-- 
2.53.0


^ permalink raw reply related

* [PATCH net v4 0/2] amt: fix use-after-free of the skb head across pulls
From: Michael Bommarito @ 2026-07-07 19:32 UTC (permalink / raw)
  To: Taehee Yoo, Andrew Lunn, David S . Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni
  Cc: netdev, linux-kernel

Several AMT receive and transmit paths cache a pointer into the skb head
and then call a helper that can reallocate that head before the cached
pointer is used again, so the later access reads or writes freed memory.

v3 addressed only the source-address reads in a subset of the handlers
and described amt_membership_query_handler() and
amt_multicast_data_handler() as unaffected.  As the review pointed out,
that was incomplete: those handlers keep stale eth_hdr() and AMT-header
pointers across later pulls, the record loops in the IGMPv3 and MLDv2
report handlers read the record count and the group record across the
*_mc_may_pull() calls, and amt_update_handler() and amt_dev_xmit() read
the destination address after further pulls.

Patch 1 walks every AMT path and, for each pointer used after a
reallocating call, either snapshots the value before the first pull or
re-derives the pointer after the last one.  This uses the re-derive
approach rather than the per-value snapshot of v3, because the write
sites cannot be expressed as a snapshot and re-derivation is already the
idiom used elsewhere in the file.

Patch 2 is a smaller, separable hardening change: the three handlers
that rewrite the ethernet header do so in place without making the head
private, which corrupts a cloned skb (for example one held by a packet
tap).  It adds skb_cow_head() before the rewrite, split out so the
use-after-free fix is not held up by discussion of the clone case.

Both patches build cleanly (x86_64, CONFIG_AMT=m, W=1) and are
checkpatch --strict clean.

Changes since v3:
 - Rework from the per-value source-address snapshot to re-deriving the
   header pointers after the last reallocating pull, and cover every
   affected handler (amt_dev_xmit, amt_multicast_data_handler,
   amt_membership_query_handler, the IGMPv3 and MLDv2 record loops, and
   the remaining reads in amt_update_handler), not just the
   source-address reads.
 - Correct the v3 commit-message claim that the query and multicast-data
   handlers were unaffected.
 - Add patch 2 (skb_cow_head() before the L2 rewrite).
 - Drop the v2 Acked-by from Taehee Yoo: this series is materially larger
   than what was acked.

v3: https://lore.kernel.org/all/20260626111917.802243-1-michael.bommarito@gmail.com/
v2: https://lore.kernel.org/all/20260617123443.3586930-1-michael.bommarito@gmail.com/

Michael Bommarito (2):
  amt: re-read skb header pointers after every pull
  amt: make the head writable before rewriting the L2 header

 drivers/net/amt.c | 117 +++++++++++++++++++++++++++++++++++++---------
 1 file changed, 96 insertions(+), 21 deletions(-)


base-commit: 5200f5f493f79f14bbdc349e402a40dfb32f23c8
-- 
2.53.0


^ permalink raw reply

* Re: [PATCH net-next v4 0/3] net: devmem: allow rx-buf-size > PAGE_SIZE per binding
From: Mina Almasry @ 2026-07-07 19:24 UTC (permalink / raw)
  To: Bobby Eshleman
  Cc: Donald Hunter, Jakub Kicinski, David S. Miller, Eric Dumazet,
	Paolo Abeni, Simon Horman, Andrew Lunn, Gerd Hoffmann,
	Vivek Kasireddy, Sumit Semwal, Christian König, Shuah Khan,
	netdev, linux-kernel, dri-devel, linux-media, linaro-mm-sig,
	linux-kselftest, sdf, razor, daniel, matttbe, skhawaja, dw,
	Joe Damato, Bobby Eshleman
In-Reply-To: <20260701-tcpdm-large-niovs-v4-0-ca4654f37570@meta.com>

(I'm kinda reviewing this very late here. Some suggestions/comments
but feel free to ignore if not useful).

On Wed, Jul 1, 2026 at 12:22 PM Bobby Eshleman <bobbyeshleman@gmail.com> wrote:
>
> Every devmem dmabuf binding hands the page_pool PAGE_SIZE niovs today.
> On NICs that consume one descriptor per netmem, this caps a single RX
> descriptor at PAGE_SIZE and burns CPU on buffer churn.
>
> In this series, we add a bind-time netlink attribute,
> NETDEV_A_DMABUF_RX_BUF_SIZE, that lets userspace request a larger niov size
> (power of two >= PAGE_SIZE).

FWIW we may be able to support arbitrary sizes with devmem. Because
the genpool supports byte-aligned allocations AFAIR. Also the
dma-mapping happens with the dma-buf size, so the actual niov size
doesn't matter. The only thing I can think off which may not be
flexible to arbitrary sizes is the driver itself. IDK what happens if
you ask the driver to dma into a buffer that is frag size 5023 or
something like that.

But that is something that can be relaxed in the future.

> Drivers must opt in via
> queue_mgmt_ops.QCFG_RX_PAGE_SIZE.
>

nit that probably doesn't matter: ...QCFG_RX_NETMEM_SIZE, or
(...NIOV_SIZE). This doesn't actually work with pages, right?

If you decide to extend to arbrary sizes, I would add to the
queue_mgmt ops supports_netmem_size(size_t size) function, and let the
driver enforce "it has to be power of 2" if it needs to. AFAICT core
doesn't need to.

> Selftests use udmabuf, but udmabuf sgtables were previously hardcoded to
> PAGE_SIZE. This series modifies udmabuf to respect folio sizes in its exported
> sgtable. The result is that when backing udmabuf with MFD_HUGETLB 2MB pages,
> the sgtable is populated with 2MB entries, allowing devmem's gen_pool to carve
> out large (eg. 64K) niovs.
>
> Measurements
> ------------
>
> Setup: kperf devmem RX/TX cuda, 4 flows, 64 MB messages, 60s, dctcp,
> num-rx-queues=4, dmabuf-rx/tx-size-mb=2048, 10 runs per niov size,
> mlx5.
>
>    niov       RX dev Gbps   RX flow avg Gbps         app sys %
>   -----  ----------------  -----------------  ----------------
>      4K  300.63 +/- 53.21    75.16 +/- 13.30   54.15 +/- 10.23
>     16K  321.35 +/- 28.20    80.34 +/-  7.05   41.05 +/-  8.87
>     32K  347.63 +/-  2.20    86.91 +/-  0.55   44.54 +/-  3.51
>     64K  332.11 +/- 14.26    83.03 +/-  3.56   35.47 +/-  3.11
>
> RX app sys % drops ~19% from 4K to 64K.
>

Hard to read the columns for me but seems like good perf data. Did
performance become worse from 32K to 64K? I wonder why.

I have some devmem performance fixes that are very critical for our
production that I haven't gotten around to upstreaming yet. I wonder
if I can send them to you for upstream submission. Are you potentially
interested?

-- 
Thanks,
Mina

^ permalink raw reply

* Re: [PATCH nf] ipvs: make destination flags atomic
From: Julian Anastasov @ 2026-07-07 19:18 UTC (permalink / raw)
  To: Yizhou Zhao
  Cc: Simon Horman, David Ahern, Ido Schimmel, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Pablo Neira Ayuso,
	Florian Westphal, Phil Sutter, Alexander Frolkin, netdev,
	lvs-devel, linux-kernel, netfilter-devel, coreteam, stable,
	Yuxiang Yang, Ao Wang, Xuewei Feng, Qi Li, Ke Xu
In-Reply-To: <20260707085706.96322-1-zhaoyz24@mails.tsinghua.edu.cn>


	Hello,

On Tue, 7 Jul 2026, Yizhou Zhao wrote:

> is_unavailable() in the SH scheduler reads dest->flags from the packet
> scheduling path while holding only the RCU read lock.  The same word is
> updated by read-modify-write operations from connection accounting and
> destination update paths, for example ip_vs_bind_dest(),
> ip_vs_unbind_dest(), and __ip_vs_update_dest().
> 
> The RCU read lock only protects the destination lifetime; it does not
> serialize accesses to dest->flags.  A racing plain load or RMW update can
> therefore observe stale state or lose an AVAILABLE/OVERLOAD bit update,
> which can make the scheduler choose an overloaded destination or report no
> available destination even though one should be usable.

	While the patch correctly serializes the concurrent
modifications for the flags, we can not claim that the scheduler
will not choose an overloaded or unavailable destination.
The patch does not change the fact that we can work with
stale data.

	We can compare 3 solutions, from fast to slow:

1. atomic_read or test_bit
	- no memory barriers for the readers
	- no memory ordering (=> stale data)

	PRO:
	- serializes RMW operations

	CON:
	- readers can use old values
	- writers may need to synchronize while changing
	the flags, eg. to check the thresholds and update the
	flags in atomic way. We do not do this.

2. Use refcount_inc_not_zero(&dest->available) from readers

	- and put the ref immediately or later:

	smp_mb__before_atomic();
	refcount_dec(&dest->available);

	- alternative: RMW such as atomic_fetch_add

	- writers can synchronize by using the IP_VS_DEST_F_AVAILABLE
	flag and then to inc/dec &dest->available when the
	flag changes
	- the same can be done for &dest->not_overloaded and
	IP_VS_DEST_F_OVERLOAD
	- PRO: readers are serialized perfectly with the
	changed value, new packets will detect the changes
	immediately
	- CON:
		- 2 full memory barriers for the readers
		- writers may need to synchronize while changing
		the flags

3. read_lock/write_lock
	- PRO: can modify more things under write lock
	- CON: full memory barriers

	With this patch you choose solution 1.
The other solutions can be expensive for the fast path.
Lets fix the commit message. Also, it is better to fix the
scripts/checkpatch.pl warnings about the 'if' conditions,
even if they are not introduced now.

Regards

--
Julian Anastasov <ja@ssi.bg>


^ permalink raw reply

* Re: [PATCH net] net: stmmac: resume PHY before reopening the interface on MTU change
From: Stefan Agner @ 2026-07-07 19:10 UTC (permalink / raw)
  To: Andrew Lunn
  Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Andrew Lunn, Russell King (Oracle), Maxime Chevallier,
	Ovidiu Panait, Maxime Coquelin, Alexandre Torgue, netdev,
	linux-stm32, linux-arm-kernel, regressions
In-Reply-To: <a6515f8c-1358-4e2a-a485-2a25a7dc6313@lunn.ch>

On 2026-07-07 20:50, Andrew Lunn wrote:
> On Tue, Jul 07, 2026 at 06:21:46PM +0200, Stefan Agner wrote:
>> Since the referenced commit, changing the MTU on a running interface no
>> longer disconnects and reconnects the PHY; __stmmac_release() merely
>> stops phylink, which also suspends the PHY (BMCR power-down) when WoL
>> is not enabled. __stmmac_open() then performs the DMA software reset in
>> stmmac_hw_setup() before phylink_start() resumes the PHY again.
>> 
>> IEEE 802.3 22.2.4.1.5 allows a PHY to stop its receive clock while
>> powered down, and stmmac requires a running receive clock for the DMA
>> software reset to complete (the phylink config sets mac_requires_rxc).
>> On such setups, e.g. the RK3566-based Home Assistant Green with an
>> RTL8211F-VD PHY in RGMII mode, any runtime MTU change now times out and
>> leaves the interface dead:
>> 
>>   rk_gmac-dwmac fe010000.ethernet end0: Failed to reset the dma
>>   rk_gmac-dwmac fe010000.ethernet end0: stmmac_hw_setup: DMA engine initialization failed
>>   rk_gmac-dwmac fe010000.ethernet end0: __stmmac_open: Hw setup failed
>>   rk_gmac-dwmac fe010000.ethernet end0: failed reopening the interface after MTU change
>> 
>> In the field this is triggered by NetworkManager applying an MTU while
>> activating the connection, breaking networking entirely.
>> 
>> Resume the PHY before reopening the interface, like stmmac_resume()
>> does, to ensure the receive clock is running for the DMA software
>> reset.
>> 
>> Fixes: db299a0c09e9 ("net: stmmac: move PHY handling out of __stmmac_open()/release()")
>> Link: https://github.com/home-assistant/operating-system/issues/4858
>> Assisted-by: Claude:claude-fable-5
>> Tested-by: Stefan Agner <stefan@agner.ch>
>> Signed-off-by: Stefan Agner <stefan@agner.ch>
>> ---
>> Note: phylink_prepare_resume()'s kernel-doc says it is to be called
>> prior to phylink_resume(); here it is paired with phylink_start()
>> (called from __stmmac_open()) instead, which phylink_resume() itself
>> uses to restart the machinery. If preferred, I can extend the
>> kernel-doc or introduce a more generically named helper.
>> 
>>  drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 9 +++++++++
>>  1 file changed, 9 insertions(+)
>> 
>> diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
>> --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
>> +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
>> @@ -5884,6 +5884,15 @@
>>  
>>  		__stmmac_release(dev);
>>  
>> +		/* phylink_stop() in __stmmac_release() suspends the PHY.
>> +		 * IEEE 802.3 allows PHYs to stop their receive clock while
>> +		 * powered down, but the DMA software reset performed by
>> +		 * stmmac_hw_setup() requires a running receive clock.
>> +		 * Resume the PHY, as on system resume, to ensure its clocks
>> +		 * are running before reopening the interface.
>> +		 */
>> +		phylink_prepare_resume(priv->phylink);
>> +
>>  		ret = __stmmac_open(dev, dma_conf);
>>  		if (ret) {
>>  			free_dma_desc_resources(priv, dma_conf);
> 
> I'm not convinced.
> 
> __stmmac_open() and __stmmac_release() should be opposites of each
> other. If __stmmac_release() stops the clock, __stmmac_open() should
> start the clock.

Hm, I see. __stmmac_release() calls phylink_stop(). But from what I can
tell we can't simply move phylink_start() in __stmmac_open() since it
does too much. So we need to use phylink_prepare_resume() in
__stmmac_open(), so there is still some asymmetry. I'll send a v2.

--
Stefan

^ permalink raw reply

* [PATCH net v2] psp: fix NULL genl_sock deref race with concurrent netns teardown
From: Kiran Kella @ 2026-07-07 18:59 UTC (permalink / raw)
  To: daniel.zahka, kuba, willemdebruijn.kernel
  Cc: davem, edumazet, pabeni, horms, weibunny, netdev, linux-kernel,
	jayakrishnan.udayavarma, ajit.khaparde, akhilesh.samineni,
	Kiran Kella, Vikas Gupta, Bhargava Marreddy, Daniel Zahka

The race occurs between network namespace removal and PSP device
unregistration.  When a netns is deleted while a PSP device associated
with that netns is concurrently being removed, psp_dev_unregister()
triggers psp_nl_notify_dev() to send a device change notification.
Concurrently, cleanup_net() running in the netns workqueue calls
genl_pernet_exit(), which sets net->genl_sock to NULL. If
genl_pernet_exit() wins the race, two sites in psp_nl_multicast_per_ns()
then dereference the NULL socket and crash:

CPU 0 (netns teardown)       CPU 1 (PSP device unregister)
======================       =============================
cleanup_net [workqueue]
  genl_pernet_exit()         psp_dev_unregister()
    net->genl_sock = NULL      psp_nl_notify_dev()
                                 psp_nl_multicast_per_ns()
                                   build_ntf()
                                     -> netlink_has_listeners(NULL)
                                     /* crash */
                                   genlmsg_multicast_netns()
                                     -> nlmsg_multicast_filtered(NULL)
                                     /* crash */

Both the main_net path (derived from psd->main_netdev) and each
assoc_net entry in psd->assoc_dev_list are affected.

Fix by replacing the bare dev_net() calls with maybe_get_net().
maybe_get_net() returns NULL if the namespace is already dying.
Holding the reference ensures genl_sock remains valid across both the
build_ntf() and genlmsg_multicast_netns() calls.

Fixes: 00c94ca2b99e ("psp: base PSP device support")
Fixes: 06c2dce2d0f6 ("psp: add new netlink cmd for dev-assoc and dev-disassoc")
Reviewed-by: Ajit Khaparde <ajit.khaparde@broadcom.com>
Reviewed-by: Vikas Gupta <vikas.gupta@broadcom.com>
Reviewed-by: Bhargava Marreddy <bhargava.marreddy@broadcom.com>
Reviewed-by: Akhilesh Samineni <akhilesh.samineni@broadcom.com>
Reviewed-by: Daniel Zahka <daniel.zahka@broadcom.com>
Tested-by: Daniel Zahka <daniel.zahka@broadcom.com>
Signed-off-by: Kiran Kella <kiran.kella@broadcom.com>
---
v2:
 - get rid of the extra struct net *net, by doing
(!maybe_get_net(assoc_net)) directly (as suggested by Daniel Zahka)

v1: https://lore.kernel.org/all/20260703112431.2860506-1-kiran.kella@broadcom.com/

 net/psp/psp_nl.c | 23 ++++++++++++++---------
 1 file changed, 14 insertions(+), 9 deletions(-)

diff --git a/net/psp/psp_nl.c b/net/psp/psp_nl.c
index 9610d8c456ff..1438dbb07949 100644
--- a/net/psp/psp_nl.c
+++ b/net/psp/psp_nl.c
@@ -62,7 +62,10 @@ psp_nl_multicast_per_ns(struct psp_dev *psd, unsigned int group,
 	struct net *main_net;
 	struct sk_buff *ntf;
 
-	main_net = dev_net(psd->main_netdev);
+	main_net = maybe_get_net(dev_net(psd->main_netdev));
+	if (!main_net)
+		return;
+
 	xa_init(&sent_nets);
 
 	list_for_each_entry(entry, &psd->assoc_dev_list, dev_list) {
@@ -77,21 +80,23 @@ psp_nl_multicast_per_ns(struct psp_dev *psd, unsigned int group,
 		if (ret == -EBUSY)
 			continue;
 
-		ntf = build_ntf(psd, assoc_net, ctx);
-		if (!ntf)
+		if (!maybe_get_net(assoc_net))
 			continue;
 
-		genlmsg_multicast_netns(&psp_nl_family, assoc_net, ntf, 0,
-					group, GFP_KERNEL);
+		ntf = build_ntf(psd, assoc_net, ctx);
+		if (ntf)
+			genlmsg_multicast_netns(&psp_nl_family, assoc_net, ntf,
+						0, group, GFP_KERNEL);
+		put_net(assoc_net);
 	}
 	xa_destroy(&sent_nets);
 
 	/* Send to main device netns */
 	ntf = build_ntf(psd, main_net, ctx);
-	if (!ntf)
-		return;
-	genlmsg_multicast_netns(&psp_nl_family, main_net, ntf, 0, group,
-				GFP_KERNEL);
+	if (ntf)
+		genlmsg_multicast_netns(&psp_nl_family, main_net, ntf, 0, group,
+					GFP_KERNEL);
+	put_net(main_net);
 }
 
 static struct sk_buff *psp_nl_clone_ntf(struct psp_dev *psd, struct net *net,
-- 
2.54.0


^ permalink raw reply related

* Re: [PATCH bpf-next v6 3/3] selftests/bpf: Add bpf_fib_lookup() VLAN flag tests
From: Emil Tsalapatis @ 2026-07-07 19:04 UTC (permalink / raw)
  To: Avinash Duduskar, ast, daniel, andrii
  Cc: eddyz87, memxor, martin.lau, song, yonghong.song, jolsa, emil,
	john.fastabend, sdf, davem, edumazet, kuba, pabeni, horms, shuah,
	hawk, yatsenko, leon.hwang, kpsingh, a.s.protopopov, ameryhung,
	rongtao, eyal.birger, bpf, netdev, linux-kernel, linux-kselftest,
	toke, dsahern
In-Reply-To: <20260704092159.1256823-4-avinash.duduskar@gmail.com>

On Sat Jul 4, 2026 at 5:21 AM EDT, Avinash Duduskar wrote:
> Cover both new VLAN flags in the fib_lookup test. BPF_FIB_LOOKUP_VLAN
> reduces a VLAN egress to its physical parent plus the tag, and
> BPF_FIB_LOOKUP_VLAN_INPUT scopes the lookup to a VLAN subinterface.
>
> BPF_FIB_LOOKUP_VLAN is XDP-only, since VLAN devices have no XDP xmit; the
> tc helper rejects it with -EINVAL, which the table runner asserts for
> every flag arm, and the egress result is checked through
> bpf_xdp_fib_lookup(). Non-VLAN cases run through both helpers and assert
> the path-independent results match; the XDP loop also checks dmac and,
> for the tot_len cases, the route mtu_result, so the VLAN-egress dmac and
> frag-needed coverage stays even though the tc path no longer reaches it.
>
> The egress arms pin the reduction (parent ifindex plus tag, including
> via a neighbour on the VLAN device, in OUTPUT mode, over a bond, and
> through a DIRECT|TBID table) and the failure contract: a stacked-VLAN
> (QinQ) egress returns BPF_FIB_LKUP_RET_VLAN_FAILURE with params->ifindex
> left at the input. That is distinct from a no-neighbour return, which
> reports the egress ifindex; only VLAN_FAILURE rewinds params->ifindex,
> and a guard arm whose input and egress devices differ pins the
> distinction. The VLAN_FAILURE arms are IPv4; the IPv6 path reaches it
> through the same shared code, so an IPv6 arm would only re-test that.
>
> The input arms use an iif rule that routes one destination to two
> gateways, so the asserted gateway reveals which device the lookup used
> as ingress, including VRF table selection through the l3mdev rule and
> l3mdev_fib_table_rcu(). A cross-netns subtest moves a VLAN device into a
> second netns while it stays registered on its parent and checks both
> directions fail closed at the boundary.
>
> A live-frames subtest (test_fib_lookup_vlan_redirect, with
> BPF_F_TEST_XDP_LIVE_FRAMES) drives real frames through the native
> xdp_do_redirect() / xdp_do_flush() path: a reducible egress is
> redirected to the parent and delivered to its peer, while a QinQ egress
> is passed to the stack, since redirecting to the VLAN device would drop
> the frame at flush (no ndo_xdp_xmit).
>
> The remaining per-case assertions are in the test table: resolution
> semantics, the -EINVAL and NOT_FWDED error arms, and the SRC/SKIP_NEIGH
> combinations.
>
> Signed-off-by: Avinash Duduskar <avinash.duduskar@gmail.com>

Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>

Nit below.

> ---
>  .../selftests/bpf/prog_tests/fib_lookup.c     | 712 +++++++++++++++++-
>  .../testing/selftests/bpf/progs/fib_lookup.c  |  36 +
>  2 files changed, 744 insertions(+), 4 deletions(-)
>
> diff --git a/tools/testing/selftests/bpf/prog_tests/fib_lookup.c b/tools/testing/selftests/bpf/prog_tests/fib_lookup.c
> index bd7658958004..18b6fdeca382 100644
> --- a/tools/testing/selftests/bpf/prog_tests/fib_lookup.c
> +++ b/tools/testing/selftests/bpf/prog_tests/fib_lookup.c

<SNIP>

> +#define VLAN_ID			100
> +#define VLAN_IFACE		"veth1.100"
> +#define VLAN_ID_DOWN		102
> +#define VLAN_IFACE_DOWN		"veth1.102"
> +#define QINQ_OUTER_IFACE	"veth1.200"
> +#define QINQ_INNER_IFACE	"veth1.200.300"
> +#define VLAN_TABLE		"300"
> +#define IPV4_VLAN_IFACE_ADDR	"10.5.0.254"
> +#define IPV4_VLAN_EGRESS_DST	"10.5.0.2"
> +#define IPV4_QINQ_DST		"10.7.0.2"
> +#define IPV4_VLAN_DST		"10.6.0.2"
> +#define IPV4_VLAN_GW		"10.5.0.1"
> +#define IPV6_VLAN_IFACE_ADDR	"fd02::254"
> +#define IPV6_VLAN_EGRESS_DST	"fd02::2"
> +#define IPV6_VLAN_DST		"fd03::2"
> +#define IPV6_VLAN_GW		"fd02::1"
> +#define VLAN_VID_UNUSED		999
> +#define VRF_IFACE		"vrf-blue"
> +#define VRF_TABLE		"1000"
> +#define VRF_VLAN_ID		101
> +#define VRF_VLAN_IFACE		"veth1.101"
> +#define IPV4_VRF_IFACE_ADDR	"10.8.0.254"
> +#define IPV4_VRF_GW		"10.8.0.1"
> +#define IPV4_VRF_DST		"10.9.0.2"
> +#define TBID_VLAN_ID		50
> +#define TBID_VLAN_IFACE		"veth2.50"
> +#define IPV4_TBID_VLAN_DST	"172.2.0.2"
> +#define IPV4_BOND_VLAN_DST	"10.11.0.2"
> +#define IPV4_VLAN_MTU_DST	"10.5.9.2"
> +#define QINQ_AD_VLAN_ID		200
> +#define QINQ_INNER_VLAN_ID	300
> +#define BOND_IFACE		"bond99"
> +#define BOND_PORT		"veth3"
> +#define BOND_PORT_PEER		"veth4"
> +#define BOND_VLAN_ID		500
>  #define DMAC			"11:11:11:11:11:11"
>  #define DMAC_INIT { 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, }
>  #define DMAC2			"01:01:01:01:01:01"


Here we add a bunch of shorthands for the topology we're setting up,
but not for all interfaces/IPs. E.g.,

<SNIP>

> +/*
> + * A VLAN device can be moved to another netns while staying registered
> + * on its parent. Neither direction may then cross the boundary: the
> + * egress flag must not publish the foreign parent's ifindex, and the
> + * input flag must fail closed rather than use a foreign ingress.
> + */
> +void test_fib_lookup_vlan_netns(void)
> +{
> +	struct bpf_fib_lookup *fib_params;
> +	struct nstoken *nstoken = NULL;
> +	struct __sk_buff skb = { };
> +	struct fib_lookup *skel = NULL;
> +	int prog_fd, xdp_fd, err, parent_idx, vlan_idx;
> +
> +	LIBBPF_OPTS(bpf_test_run_opts, run_opts,
> +		    .data_in = &pkt_v6,
> +		    .data_size_in = sizeof(pkt_v6),
> +		    .ctx_in = &skb,
> +		    .ctx_size_in = sizeof(skb),
> +	);
> +	LIBBPF_OPTS(bpf_test_run_opts, xdp_opts,
> +		    .data_in = &pkt_v6,
> +		    .data_size_in = sizeof(pkt_v6),
> +	);
> +
> +	skel = fib_lookup__open_and_load();
> +	if (!ASSERT_OK_PTR(skel, "skel open_and_load"))
> +		return;
> +	prog_fd = bpf_program__fd(skel->progs.fib_lookup);
> +	xdp_fd = bpf_program__fd(skel->progs.fib_lookup_xdp);
> +	fib_params = &skel->bss->fib_params;
> +
> +	SYS(fail, "ip netns add %s", NS_VLAN_A);
> +	SYS(fail, "ip netns add %s", NS_VLAN_B);
> +
> +	nstoken = open_netns(NS_VLAN_A);
> +	if (!ASSERT_OK_PTR(nstoken, "open_netns(a)"))
> +		goto fail;
> +
> +	SYS(fail, "ip link add veth7 type veth peer name veth8");
> +	SYS(fail, "ip link set dev veth7 up");
> +	SYS(fail, "ip link add link veth7 name veth7.66 type vlan id 66");
> +	SYS(fail, "ip link set veth7.66 netns %s", NS_VLAN_B);
> +
> +	parent_idx = if_nametoindex("veth7");
> +	if (!ASSERT_NEQ(parent_idx, 0, "if_nametoindex(veth7)"))
> +		goto fail;
> +
> +	/*
> +	 * input: the moved device is still in veth7's VLAN group, but it
> +	 * lives in another netns, so the lookup must fail closed
> +	 */
> +	skb.ifindex = parent_idx;
> +	memset(fib_params, 0, sizeof(*fib_params));
> +	fib_params->family = AF_INET;
> +	fib_params->l4_protocol = IPPROTO_TCP;
> +	fib_params->ifindex = parent_idx;
> +	fib_params->h_vlan_proto = htons(ETH_P_8021Q);
> +	fib_params->h_vlan_TCI = htons(66);
> +	if (!ASSERT_EQ(inet_pton(AF_INET, "10.66.0.2", &fib_params->ipv4_dst),
> +		       1, "inet_pton(dst)"))
> +		goto fail;
> +

The IPs are hardcoded here (and in the rest of the code below). AFAICT the
logic is that the defines are to make it more obvious as to what the test
is about. Is there a logic of this kind behind the naming convention?

> +	skel->bss->fib_lookup_ret = -1;
> +	skel->bss->lookup_flags = BPF_FIB_LOOKUP_VLAN_INPUT |
> +				  BPF_FIB_LOOKUP_SKIP_NEIGH;
> +	err = bpf_prog_test_run_opts(prog_fd, &run_opts);
> +	if (!ASSERT_OK(err, "test_run(input)"))
> +		goto fail;
> +	ASSERT_EQ(skel->bss->fib_lookup_ret, BPF_FIB_LKUP_RET_NOT_FWDED,
> +		  "input across netns fails closed");
> +	ASSERT_EQ(fib_params->ifindex, parent_idx, "ifindex untouched");
> +	ASSERT_EQ(fib_params->h_vlan_TCI, htons(66), "tag untouched");
> +
> +	close_netns(nstoken);
> +	nstoken = open_netns(NS_VLAN_B);
> +	if (!ASSERT_OK_PTR(nstoken, "open_netns(b)"))
> +		goto fail;
> +
> +	/*
> +	 * egress: the fib result is the VLAN device here, but its parent
> +	 * is in the other netns, so the swap must not happen
> +	 */
> +	SYS(fail, "ip link set dev veth7.66 up");
> +	SYS(fail, "ip addr add 10.66.0.1/24 dev veth7.66");
> +	err = write_sysctl("/proc/sys/net/ipv4/conf/veth7.66/forwarding", "1");
> +	if (!ASSERT_OK(err, "write_sysctl(forwarding)"))
> +		goto fail;
> +
> +	vlan_idx = if_nametoindex("veth7.66");
> +	if (!ASSERT_NEQ(vlan_idx, 0, "if_nametoindex(veth7.66)"))
> +		goto fail;
> +
> +	memset(fib_params, 0, sizeof(*fib_params));
> +	fib_params->family = AF_INET;
> +	fib_params->l4_protocol = IPPROTO_TCP;
> +	fib_params->ifindex = vlan_idx;
> +	if (!ASSERT_EQ(inet_pton(AF_INET, "10.66.0.2", &fib_params->ipv4_dst),
> +		       1, "inet_pton(dst)") ||
> +	    !ASSERT_EQ(inet_pton(AF_INET, "10.66.0.1", &fib_params->ipv4_src),
> +		       1, "inet_pton(src)"))
> +		goto fail;

<SNIP>

^ permalink raw reply

* Re: [PATCH net 2/2] net/stmmac: Verify provided DTS AXI setup
From: Andrew Lunn @ 2026-07-07 19:04 UTC (permalink / raw)
  To: Jakub Raczynski
  Cc: netdev, k.tegowski, k.domagalski, andrew+netdev, davem, edumazet,
	kuba, pabeni, mcoquelin.stm32, alexandre.torgue, linux-stm32,
	linux-arm-kernel, linux-kernel
In-Reply-To: <20260707174431.1264520-3-j.raczynski@samsung.com>

On Tue, Jul 07, 2026 at 07:44:31PM +0200, Jakub Raczynski wrote:
> During parsing of AXI setup, there are few issues:
> - 'axi_blen' array is uninitialized value on stack without zero-init stack
>   configured. This can result in random AXI burst length config if
>   DTS config provides shorter array than AXI_BLEN.

What does the DT blinding say about the length? Is it allowed to be
short?

Are we talking about:

      snps,blen:
        $ref: /schemas/types.yaml#/definitions/uint32-array
        description:
          this is a vector of supported burst length.
        minItems: 7
        maxItems: 7

So it should be 7. Are there any in kernel DT blobs which don't pass
7? Can we just error out when it is not 7?

> - In case of failed memory allocation for AXI and error, there is no handling
>   of that. Fix it by checking if AXI config is error and return if so,
>   as this can only lack of memory. No AXI config, although is probably
>   wrong in most cases, is not treated as error, as generic config is mostly
>   provided in drivers.

This seems like a different fix. Maybe put it into a patch of its own.

> Fixes: afea03656add ("stmmac: rework DMA bus setting and introduce new platform AXI structure")

Again, does this bother anybody? At least axi_blen issue seems to be
that the DT blob is broken, so i doubt it actually does. Are there
reports of memory allocation error and resulting Opps.


    Andrew

---
pw-bot: cr

^ permalink raw reply

* Re: [PATCH net 1/2] net/stmmac: Protect against zero queue DTS config
From: Andrew Lunn @ 2026-07-07 18:57 UTC (permalink / raw)
  To: Jakub Raczynski
  Cc: netdev, k.tegowski, k.domagalski, andrew+netdev, davem, edumazet,
	kuba, pabeni, mcoquelin.stm32, alexandre.torgue, linux-stm32,
	linux-arm-kernel, linux-kernel
In-Reply-To: <20260707174431.1264520-2-j.raczynski@samsung.com>

On Tue, Jul 07, 2026 at 07:44:30PM +0200, Jakub Raczynski wrote:
> Commit 8a7bca6de6de protected against inputing number of tx/rx_queues_to_use
> over kernel supported limit in DTS config. AI review mentioned that we also
> should protect against zero queue input, because this would cause issues
> down the line. Missing config is not an issue as stmmac_plat_dat_alloc()
> does apply '1' by default.
> 
> Fix this by adding check for zero queues input during DTS parsing
> 
> Fixes: 8a7bca6de6de ("net/stmmac: Apply MTL_MAX queue limit if config missing")

I'm not sure a Fixes: is justified here. Does this bother somebody? As
far as i understand, for this to actually do something the system is
broken anyway?

>  	if (!of_property_read_u32(rx_node, "snps,rx-queues-to-use", &value)) {
>  		if (value > MTL_MAX_RX_QUEUES)
>  			value = MTL_MAX_RX_QUEUES;
> +		else if (value == 0)
> +			value = 1;

If the DT is broken, don't we want it to be fixed? -EINVAL would make
it obvious.

    Andrew

---
pw-bot: cr


^ permalink raw reply

* Re: [PATCH net] net: stmmac: resume PHY before reopening the interface on MTU change
From: Stefan Agner @ 2026-07-07 18:57 UTC (permalink / raw)
  To: Jakub Raczynski
  Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Andrew Lunn, Russell King (Oracle), Maxime Chevallier,
	Ovidiu Panait, Maxime Coquelin, Alexandre Torgue, netdev,
	linux-stm32, linux-arm-kernel, regressions
In-Reply-To: <ak01QZhCKkq2FM33@AMDC4622.eu.corp.samsungelectronics.net>

On 2026-07-07 19:20, Jakub Raczynski wrote:
> On Tue, Jul 07, 2026 at 06:21:46PM +0200, Stefan Agner wrote:
>> diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
>> --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
>> +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
>> @@ -5884,6 +5884,15 @@
>> 
>>  		__stmmac_release(dev);
>> 
>> +		/* phylink_stop() in __stmmac_release() suspends the PHY.
>> +		 * IEEE 802.3 allows PHYs to stop their receive clock while
>> +		 * powered down, but the DMA software reset performed by
>> +		 * stmmac_hw_setup() requires a running receive clock.
>> +		 * Resume the PHY, as on system resume, to ensure its clocks
>> +		 * are running before reopening the interface.
>> +		 */
>> +		phylink_prepare_resume(priv->phylink);
> 
> Does it work without warnings? Nothing in dmesg?

With this patch applied, this is the dmesg log on MTU change:
[   56.761175] rk_gmac-dwmac fe010000.ethernet end0: Register
MEM_TYPE_PAGE_POOL RxQ-0
[   56.762769] rk_gmac-dwmac fe010000.ethernet end0: Link is Down
[   56.801327] dwmac4: Master AXI performs any burst length
[   56.801368] rk_gmac-dwmac fe010000.ethernet end0: No Safety Features
support found
[   56.801409] rk_gmac-dwmac fe010000.ethernet end0: IEEE 1588-2008
Advanced Timestamp supported
[   56.801780] rk_gmac-dwmac fe010000.ethernet end0: registered PTP
clock
[   56.801804] rk_gmac-dwmac fe010000.ethernet end0: configuring for
phy/rgmii link mode
[   61.032985] rk_gmac-dwmac fe010000.ethernet end0: Link is Up -
1Gbps/Full - flow control off

> phylink_prepare_resume() does have ASSERT_RTNL() which is not called anywhere.

I am not very familiar with the codebase, but Fable states:

> RTNL is held here: ndo_change_mtu is invoked from netif_set_mtu_ext(),
> which calls netdev_ops_assert_locked() — for a driver without instance
> locking such as stmmac that is ASSERT_RTNL() — right before calling the
> driver op. 

What I can definitely confirm is that without this patch applied, a MTU
change does not succeed, with it applied MTU change works and the
network interface remains functional.

--
Stefan

^ permalink raw reply

* Re: [PATCH net] net: stmmac: resume PHY before reopening the interface on MTU change
From: Andrew Lunn @ 2026-07-07 18:50 UTC (permalink / raw)
  To: Stefan Agner
  Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Andrew Lunn, Russell King (Oracle), Maxime Chevallier,
	Ovidiu Panait, Maxime Coquelin, Alexandre Torgue, netdev,
	linux-stm32, linux-arm-kernel, regressions
In-Reply-To: <20260707162146.73823-1-stefan@agner.ch>

On Tue, Jul 07, 2026 at 06:21:46PM +0200, Stefan Agner wrote:
> Since the referenced commit, changing the MTU on a running interface no
> longer disconnects and reconnects the PHY; __stmmac_release() merely
> stops phylink, which also suspends the PHY (BMCR power-down) when WoL
> is not enabled. __stmmac_open() then performs the DMA software reset in
> stmmac_hw_setup() before phylink_start() resumes the PHY again.
> 
> IEEE 802.3 22.2.4.1.5 allows a PHY to stop its receive clock while
> powered down, and stmmac requires a running receive clock for the DMA
> software reset to complete (the phylink config sets mac_requires_rxc).
> On such setups, e.g. the RK3566-based Home Assistant Green with an
> RTL8211F-VD PHY in RGMII mode, any runtime MTU change now times out and
> leaves the interface dead:
> 
>   rk_gmac-dwmac fe010000.ethernet end0: Failed to reset the dma
>   rk_gmac-dwmac fe010000.ethernet end0: stmmac_hw_setup: DMA engine initialization failed
>   rk_gmac-dwmac fe010000.ethernet end0: __stmmac_open: Hw setup failed
>   rk_gmac-dwmac fe010000.ethernet end0: failed reopening the interface after MTU change
> 
> In the field this is triggered by NetworkManager applying an MTU while
> activating the connection, breaking networking entirely.
> 
> Resume the PHY before reopening the interface, like stmmac_resume()
> does, to ensure the receive clock is running for the DMA software
> reset.
> 
> Fixes: db299a0c09e9 ("net: stmmac: move PHY handling out of __stmmac_open()/release()")
> Link: https://github.com/home-assistant/operating-system/issues/4858
> Assisted-by: Claude:claude-fable-5
> Tested-by: Stefan Agner <stefan@agner.ch>
> Signed-off-by: Stefan Agner <stefan@agner.ch>
> ---
> Note: phylink_prepare_resume()'s kernel-doc says it is to be called
> prior to phylink_resume(); here it is paired with phylink_start()
> (called from __stmmac_open()) instead, which phylink_resume() itself
> uses to restart the machinery. If preferred, I can extend the
> kernel-doc or introduce a more generically named helper.
> 
>  drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 9 +++++++++
>  1 file changed, 9 insertions(+)
> 
> diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
> --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
> +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
> @@ -5884,6 +5884,15 @@
>  
>  		__stmmac_release(dev);
>  
> +		/* phylink_stop() in __stmmac_release() suspends the PHY.
> +		 * IEEE 802.3 allows PHYs to stop their receive clock while
> +		 * powered down, but the DMA software reset performed by
> +		 * stmmac_hw_setup() requires a running receive clock.
> +		 * Resume the PHY, as on system resume, to ensure its clocks
> +		 * are running before reopening the interface.
> +		 */
> +		phylink_prepare_resume(priv->phylink);
> +
>  		ret = __stmmac_open(dev, dma_conf);
>  		if (ret) {
>  			free_dma_desc_resources(priv, dma_conf);

I'm not convinced.

__stmmac_open() and __stmmac_release() should be opposites of each
other. If __stmmac_release() stops the clock, __stmmac_open() should
start the clock.

	Andrew


^ permalink raw reply

* Re: [PATCH net] gve: fix Rx queue stall on alloc failure
From: Maciej Fijalkowski @ 2026-07-07 18:41 UTC (permalink / raw)
  To: Eddie Phillips
  Cc: Harshitha Ramamurthy, netdev, joshwash, andrew+netdev, davem,
	edumazet, kuba, pabeni, ast, daniel, hawk, john.fastabend, bpf,
	sdf, willemb, jordanrhee, nktgrg, maolson, jacob.e.keller,
	thostet, csully, bcf, linux-kernel, stable
In-Reply-To: <CAPBb8HnXL-G692yRZYatA2m=X_YpNWG2eDhQzzM+Enq1apyC8Q@mail.gmail.com>

On Tue, Jul 07, 2026 at 10:28:59AM -0700, Eddie Phillips wrote:
> On Fri, Jul 3, 2026 at 9:06 AM Maciej Fijalkowski
> <maciej.fijalkowski@intel.com> wrote:
> >
> > On Fri, Jul 03, 2026 at 01:03:20AM -0700, Eddie Phillips wrote:
> > > > I think this deserves to be pulled out of the timer logic?
> > >
> > > If by this you mean pull the stats into a separate patch, I agree.
> >
> > Hi Eddie,
> >
> > instead of forming a response at the top of the mail, please have your
> > answers inlined; it is preferred way of communication on mailing lists.
> >
> > >
> > > > - couldn't you detect this case within napi poll loop?
> > >
> > > It can only be detected after attempting to refill the queue and finding
> > > that we are still below the critical threshold.
> > >
> > > > - if not, does it have to be per-q timer? wouldn't one global per pf timer
> > > >   satisfy your needs?
> > >
> > > There are a few ways a global timer could be implemented,
> > >  - The global timer could queue napi for *all* queues, which would
> > > result in a lot of unnecessary work.
> > >  - The global timer could iterate over each queue and try to detect
> > > the critical low buffer condition, however this would require
> > > introducing synchronization between the timer and the napis, which
> > > would introduce expensive locking into the hot path.
> > >  - The global timer could be paired with a bitmap that stores which
> > > queues need to be serviced.
> >
> > bitmap would probably do the job but i won't insist here tho.
> >
> > One more question/idea:
> > Before arming the starvation timer, could we first try to make a smaller batch
> > of already-posted buffers visible to HW?
> 
> The maximum number of descriptors that a single RSC packet can consume
> is 19, so 8 descriptors isn't enough to receive a maximum-sized RSC
> packet. If the hardware runs out of buffers, it is supposed to close

standard MAX_SKB_FRAGS is not enough either. are you actually receiving
that maxed out packets on your setup? What I suggested would probably help
at standard mtu traffic, but I think it's enough of discussing.

> the RSC window and flush the descriptors, but operating this close to
> the hardware's limits could be risky in case there are HW bugs or edge
> cases we're unaware of. I think building in a safety margin would be more
> robust.
> 
> > It seems the HW can accept RX buffer tail doorbell updates at a granularity
> > lower than the normal `GVE_RX_BUF_THRESH_DQO` batching threshold, apparently as
> > low as 8 descriptors. If that is the case, could we first use this as an
> > emergency low-watermark path: when refill posts at least 8 descriptors but does
> > not reach the normal 32-descriptor threshold, ring the doorbell immediately and
> > only arm the starvation timer if even that lower threshold cannot be reached?
> >
> > >
> > > A `struct timer_list` is only 40 bytes, so the current implemention is
> > > not expensive. Though a global timer is valid, it's not strictly better.
> > >
> > > That said, I agree that we can clean up the structure—I will move the
> > > timer state from the individual RX rings to the `gve_priv` structure.
> > >
> > > On Wed, Jul 1, 2026 at 6:22 AM Maciej Fijalkowski
> > > <maciej.fijalkowski@intel.com> wrote:
> > > >
> > > > On Wed, Jul 01, 2026 at 12:53:41AM +0000, Harshitha Ramamurthy wrote:
> > > > > From: Eddie Phillips <eddiephillips@google.com>
> > > > >
> > > > > When the system is under extreme memory pressure, page allocations can
> > > > > fail during the Rx buffer refill loop. If the number of buffers posted
> > > > > to hardware falls below a critical low threshold and the refill loop
> > > > > exits due to allocation failures, the queue can stall:
> > > > >
> > > > > 1. The device drops incoming packets because there are no descriptors.
> > > > > 2. Since no packets are processed, no Rx completions are generated.
> > > > > 3. Because no completions occur, NAPI is never scheduled, preventing
> > > > >    the refill loop from running again even after memory is freed.
> > > > >
> > > > > This results in a permanent queue stall.
> > > > >
> > > > > Resolve this by introducing a starvation recovery timer for each Rx queue.
> > > > > If the number of buffers posted to hardware falls below a critical low
> > > > > threshold, start a timer to periodically reschedule NAPI. Once NAPI runs
> > > > > and successfully refills the queue above the threshold, the timer is
> > > > > not rescheduled.
> > > > >
> > > > > Also add a new ethtool statistic "rx_critical_low_bufs" to track the
> > > > > number of times the starvation recovery timer is triggered.
> > > >
> > > > I think this deserves to be pulled out of the timer logic?
> > > >
> > > > Two questions tho:
> > > > - couldn't you detect this case within napi poll loop?
> > > > - if not, does it have to be per-q timer? wouldn't one global per pf timer
> > > >   satisfy your needs?
> > > >
> > > > >
> > > > > Cc: stable@vger.kernel.org
> > > > > Fixes: 9b8dd5e5ea48 ("gve: DQO: Add RX path")
> > > > > Reviewed-by: Jordan Rhee <jordanrhee@google.com>
> > > > > Signed-off-by: Eddie Phillips <eddiephillips@google.com>
> > > > > Signed-off-by: Harshitha Ramamurthy <hramamurthy@google.com>
> > > > > ---
> > > > >  drivers/net/ethernet/google/gve/gve.h         |  4 ++++
> > > > >  drivers/net/ethernet/google/gve/gve_ethtool.c | 14 +++++++++++++-
> > > > >  drivers/net/ethernet/google/gve/gve_rx_dqo.c  | 32 ++++++++++++++++++++++++++++++++
> > > > >  3 files changed, 49 insertions(+), 1 deletion(-)
> > > > >
> > > > > diff --git a/drivers/net/ethernet/google/gve/gve.h b/drivers/net/ethernet/google/gve/gve.h
> > > > > index 2f7bd330..8378bef2 100644
> > > > > --- a/drivers/net/ethernet/google/gve/gve.h
> > > > > +++ b/drivers/net/ethernet/google/gve/gve.h
> > > > > @@ -13,6 +13,7 @@
> > > > >  #include <linux/netdevice.h>
> > > > >  #include <linux/net_tstamp.h>
> > > > >  #include <linux/pci.h>
> > > > > +#include <linux/timer.h>
> > > > >  #include <linux/ptp_clock_kernel.h>
> > > > >  #include <linux/u64_stats_sync.h>
> > > > >  #include <net/page_pool/helpers.h>
> > > > > @@ -41,6 +42,7 @@
> > > > >
> > > > >  /* Interval to schedule a stats report update, 20000ms. */
> > > > >  #define GVE_STATS_REPORT_TIMER_PERIOD        20000
> > > > > +#define GVE_RX_NAPI_RESCHED_MS 20 /* msecs */
> > > > >
> > > > >  /* Numbers of NIC tx/rx stats in stats report. */
> > > > >  #define NIC_TX_STATS_REPORT_NUM      0
> > > > > @@ -318,6 +320,7 @@ struct gve_rx_ring {
> > > > >       u64 rx_copied_pkt; /* free-running total number of copied packets */
> > > > >       u64 rx_skb_alloc_fail; /* free-running count of skb alloc fails */
> > > > >       u64 rx_buf_alloc_fail; /* free-running count of buffer alloc fails */
> > > > > +     u64 rx_critical_low_bufs; /* count of critical low buffer events */
> > > > >       u64 rx_desc_err_dropped_pkt; /* free-running count of packets dropped by descriptor error */
> > > > >       /* free-running count of unsplit packets due to header buffer overflow or hdr_len is 0 */
> > > > >       u64 rx_hsplit_unsplit_pkt;
> > > > > @@ -334,6 +337,7 @@ struct gve_rx_ring {
> > > > >       struct gve_queue_resources *q_resources; /* head and tail pointer idx */
> > > > >       dma_addr_t q_resources_bus; /* dma address for the queue resources */
> > > > >       struct u64_stats_sync statss; /* sync stats for 32bit archs */
> > > > > +     struct timer_list starvation_timer; /* for queue starvation recovery */
> > > > >
> > > > >       struct gve_rx_ctx ctx; /* Info for packet currently being processed in this ring. */
> > > > >
> > > > > diff --git a/drivers/net/ethernet/google/gve/gve_ethtool.c b/drivers/net/ethernet/google/gve/gve_ethtool.c
> > > > > index a0e0472b..71b6efbf 100644
> > > > > --- a/drivers/net/ethernet/google/gve/gve_ethtool.c
> > > > > +++ b/drivers/net/ethernet/google/gve/gve_ethtool.c
> > > > > @@ -46,6 +46,7 @@ static const char gve_gstrings_main_stats[][ETH_GSTRING_LEN] = {
> > > > >       "rx_hsplit_unsplit_pkt",
> > > > >       "interface_up_cnt", "interface_down_cnt", "reset_cnt",
> > > > >       "page_alloc_fail", "dma_mapping_error", "stats_report_trigger_cnt",
> > > > > +     "rx_critical_low_bufs",
> > > > >  };
> > > > >
> > > > >  static const char gve_gstrings_rx_stats[][ETH_GSTRING_LEN] = {
> > > > > @@ -58,6 +59,7 @@ static const char gve_gstrings_rx_stats[][ETH_GSTRING_LEN] = {
> > > > >       "rx_xdp_aborted[%u]", "rx_xdp_drop[%u]", "rx_xdp_pass[%u]",
> > > > >       "rx_xdp_tx[%u]", "rx_xdp_redirect[%u]",
> > > > >       "rx_xdp_tx_errors[%u]", "rx_xdp_redirect_errors[%u]", "rx_xdp_alloc_fails[%u]",
> > > > > +     "rx_critical_low_bufs[%u]",
> > > > >  };
> > > > >
> > > > >  static const char gve_gstrings_tx_stats[][ETH_GSTRING_LEN] = {
> > > > > @@ -151,12 +153,14 @@ gve_get_ethtool_stats(struct net_device *netdev,
> > > > >  {
> > > > >       u64 tmp_rx_pkts, tmp_rx_hsplit_pkt, tmp_rx_bytes, tmp_rx_hsplit_bytes,
> > > > >               tmp_rx_skb_alloc_fail, tmp_rx_buf_alloc_fail,
> > > > > +             tmp_rx_critical_low_bufs,
> > > > >               tmp_rx_desc_err_dropped_pkt, tmp_rx_hsplit_unsplit_pkt,
> > > > >               tmp_tx_pkts, tmp_tx_bytes,
> > > > >               tmp_xdp_tx_errors, tmp_xdp_redirect_errors;
> > > > >       u64 rx_buf_alloc_fail, rx_desc_err_dropped_pkt, rx_hsplit_unsplit_pkt,
> > > > >               rx_pkts, rx_hsplit_pkt, rx_skb_alloc_fail, rx_bytes, tx_pkts, tx_bytes,
> > > > > -             tx_dropped, xdp_tx_errors, xdp_redirect_errors;
> > > > > +             rx_critical_low_bufs, tx_dropped, xdp_tx_errors,
> > > > > +             xdp_redirect_errors;
> > > > >       int rx_base_stats_idx, max_rx_stats_idx, max_tx_stats_idx;
> > > > >       int stats_idx, stats_region_len, nic_stats_len;
> > > > >       struct stats *report_stats;
> > > > > @@ -197,6 +201,7 @@ gve_get_ethtool_stats(struct net_device *netdev,
> > > > >
> > > > >       for (rx_pkts = 0, rx_bytes = 0, rx_hsplit_pkt = 0,
> > > > >            rx_skb_alloc_fail = 0, rx_buf_alloc_fail = 0,
> > > > > +          rx_critical_low_bufs = 0,
> > > > >            rx_desc_err_dropped_pkt = 0, rx_hsplit_unsplit_pkt = 0,
> > > > >            xdp_tx_errors = 0, xdp_redirect_errors = 0,
> > > > >            ring = 0;
> > > > > @@ -212,6 +217,8 @@ gve_get_ethtool_stats(struct net_device *netdev,
> > > > >                               tmp_rx_bytes = rx->rbytes;
> > > > >                               tmp_rx_skb_alloc_fail = rx->rx_skb_alloc_fail;
> > > > >                               tmp_rx_buf_alloc_fail = rx->rx_buf_alloc_fail;
> > > > > +                             tmp_rx_critical_low_bufs =
> > > > > +                                     rx->rx_critical_low_bufs;
> > > > >                               tmp_rx_desc_err_dropped_pkt =
> > > > >                                       rx->rx_desc_err_dropped_pkt;
> > > > >                               tmp_rx_hsplit_unsplit_pkt =
> > > > > @@ -226,6 +233,7 @@ gve_get_ethtool_stats(struct net_device *netdev,
> > > > >                       rx_bytes += tmp_rx_bytes;
> > > > >                       rx_skb_alloc_fail += tmp_rx_skb_alloc_fail;
> > > > >                       rx_buf_alloc_fail += tmp_rx_buf_alloc_fail;
> > > > > +                     rx_critical_low_bufs += tmp_rx_critical_low_bufs;
> > > > >                       rx_desc_err_dropped_pkt += tmp_rx_desc_err_dropped_pkt;
> > > > >                       rx_hsplit_unsplit_pkt += tmp_rx_hsplit_unsplit_pkt;
> > > > >                       xdp_tx_errors += tmp_xdp_tx_errors;
> > > > > @@ -269,6 +277,7 @@ gve_get_ethtool_stats(struct net_device *netdev,
> > > > >       data[i++] = priv->page_alloc_fail;
> > > > >       data[i++] = priv->dma_mapping_error;
> > > > >       data[i++] = priv->stats_report_trigger_cnt;
> > > > > +     data[i++] = rx_critical_low_bufs;
> > > > >       i = GVE_MAIN_STATS_LEN;
> > > > >
> > > > >       rx_base_stats_idx = 0;
> > > > > @@ -337,6 +346,8 @@ gve_get_ethtool_stats(struct net_device *netdev,
> > > > >                               tmp_rx_hsplit_bytes = rx->rx_hsplit_bytes;
> > > > >                               tmp_rx_skb_alloc_fail = rx->rx_skb_alloc_fail;
> > > > >                               tmp_rx_buf_alloc_fail = rx->rx_buf_alloc_fail;
> > > > > +                             tmp_rx_critical_low_bufs =
> > > > > +                                     rx->rx_critical_low_bufs;
> > > > >                               tmp_rx_desc_err_dropped_pkt =
> > > > >                                       rx->rx_desc_err_dropped_pkt;
> > > > >                               tmp_xdp_tx_errors = rx->xdp_tx_errors;
> > > > > @@ -381,6 +392,7 @@ gve_get_ethtool_stats(struct net_device *netdev,
> > > > >                       } while (u64_stats_fetch_retry(&priv->rx[ring].statss,
> > > > >                                                      start));
> > > > >                       i += GVE_XDP_ACTIONS + 3; /* XDP rx counters */
> > > > > +                     data[i++] = tmp_rx_critical_low_bufs;
> > > > >               }
> > > > >       } else {
> > > > >               i += priv->rx_cfg.num_queues * NUM_GVE_RX_CNTS;
> > > > > diff --git a/drivers/net/ethernet/google/gve/gve_rx_dqo.c b/drivers/net/ethernet/google/gve/gve_rx_dqo.c
> > > > > index 02cba280..303db4fa 100644
> > > > > --- a/drivers/net/ethernet/google/gve/gve_rx_dqo.c
> > > > > +++ b/drivers/net/ethernet/google/gve/gve_rx_dqo.c
> > > > > @@ -18,6 +18,16 @@
> > > > >  #include <net/tcp.h>
> > > > >  #include <net/xdp_sock_drv.h>
> > > > >
> > > > > +static void gve_rx_starvation_timer(struct timer_list *t)
> > > > > +{
> > > > > +     struct gve_rx_ring *rx = timer_container_of(rx, t, starvation_timer);
> > > > > +     struct gve_priv *priv = rx->gve;
> > > > > +     struct gve_notify_block *block;
> > > > > +
> > > > > +     block = &priv->ntfy_blocks[rx->ntfy_id];
> > > > > +     napi_schedule(&block->napi);
> > > > > +}
> > > > > +
> > > > >  static void gve_rx_free_hdr_bufs(struct gve_priv *priv, struct gve_rx_ring *rx)
> > > > >  {
> > > > >       struct device *hdev = &priv->pdev->dev;
> > > > > @@ -120,6 +130,7 @@ void gve_rx_stop_ring_dqo(struct gve_priv *priv, int idx)
> > > > >
> > > > >       if (rx->dqo.page_pool)
> > > > >               page_pool_disable_direct_recycling(rx->dqo.page_pool);
> > > > > +     timer_delete_sync(&rx->starvation_timer);
> > > > >       gve_remove_napi(priv, ntfy_idx);
> > > > >       gve_rx_remove_from_block(priv, idx);
> > > > >       gve_rx_reset_ring_dqo(priv, idx);
> > > > > @@ -136,6 +147,8 @@ void gve_rx_free_ring_dqo(struct gve_priv *priv, struct gve_rx_ring *rx,
> > > > >       u32 qpl_id;
> > > > >       int i;
> > > > >
> > > > > +     timer_shutdown_sync(&rx->starvation_timer);
> > > > > +
> > > > >       completion_queue_slots = rx->dqo.complq.mask + 1;
> > > > >       buffer_queue_slots = rx->dqo.bufq.mask + 1;
> > > > >
> > > > > @@ -232,6 +245,7 @@ int gve_rx_alloc_ring_dqo(struct gve_priv *priv,
> > > > >       rx->gve = priv;
> > > > >       rx->q_num = idx;
> > > > >       rx->packet_buffer_size = cfg->packet_buffer_size;
> > > > > +     timer_setup(&rx->starvation_timer, gve_rx_starvation_timer, 0);
> > > > >
> > > > >       if (cfg->xdp) {
> > > > >               rx->packet_buffer_truesize = GVE_XDP_RX_BUFFER_SIZE_DQO;
> > > > > @@ -365,6 +379,7 @@ void gve_rx_post_buffers_dqo(struct gve_rx_ring *rx)
> > > > >       struct gve_rx_compl_queue_dqo *complq = &rx->dqo.complq;
> > > > >       struct gve_rx_buf_queue_dqo *bufq = &rx->dqo.bufq;
> > > > >       struct gve_priv *priv = rx->gve;
> > > > > +     u32 num_bufs_avail_to_hw;
> > > > >       u32 num_avail_slots;
> > > > >       u32 num_full_slots;
> > > > >       u32 num_posted = 0;
> > > > > @@ -400,6 +415,23 @@ void gve_rx_post_buffers_dqo(struct gve_rx_ring *rx)
> > > > >       }
> > > > >
> > > > >       rx->fill_cnt += num_posted;
> > > > > +
> > > > > +     /* If the queue has fewer than GVE_RX_BUF_THRESH_DQO descriptors
> > > > > +      * visible to the hardware, and no doorbell was written, the hardware
> > > > > +      * is in danger of starving and cannot trigger interrupts. Start the
> > > > > +      * timer to periodically reschedule NAPI and recover from starvation.
> > > > > +      */
> > > > > +     num_bufs_avail_to_hw =
> > > > > +             ((bufq->tail & ~(GVE_RX_BUF_THRESH_DQO - 1)) -
> > > > > +              bufq->head) & bufq->mask;
> > > > > +
> > > > > +     if (num_bufs_avail_to_hw < GVE_RX_BUF_THRESH_DQO) {
> > > > > +             u64_stats_update_begin(&rx->statss);
> > > > > +             rx->rx_critical_low_bufs++;
> > > > > +             u64_stats_update_end(&rx->statss);
> > > > > +             mod_timer(&rx->starvation_timer,
> > > > > +                       jiffies + msecs_to_jiffies(GVE_RX_NAPI_RESCHED_MS));
> > > > > +     }
> > > > >  }
> > > > >
> > > > >  static void gve_rx_skb_csum(struct sk_buff *skb,
> > > > > --
> > > > > 2.55.0.rc2.803.g1fd1e6609c-goog
> > > > >
> > > > >

^ permalink raw reply

* [PATCH] nfc: llcp: Fix nfc_dev refcount leak in connect
From: Shuangpeng Bai @ 2026-07-07 18:35 UTC (permalink / raw)
  To: david, oe-linux-nfc
  Cc: davem, edumazet, kuba, pabeni, horms, netdev, linux-kernel,
	Shuangpeng Bai

llcp_sock_connect() takes a reference to the NFC device with
nfc_get_device() and stores it in llcp_sock->dev before moving the
socket to LLCP_CONNECTING.

If such a pending connection is released before reaching
LLCP_CONNECTED, llcp_sock_destruct() does not drop that device
reference because it only handles connected sockets. This leaks the
nfc_dev reference acquired during connect.

Drop the device reference for LLCP_CONNECTING sockets as well.

Signed-off-by: Shuangpeng Bai <shuangpeng.kernel@gmail.com>
---
 net/nfc/llcp_sock.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/net/nfc/llcp_sock.c b/net/nfc/llcp_sock.c
index feab29fc62f4..44639ccd438e 100644
--- a/net/nfc/llcp_sock.c
+++ b/net/nfc/llcp_sock.c
@@ -960,7 +960,8 @@ static void llcp_sock_destruct(struct sock *sk)
 
 	pr_debug("%p\n", sk);
 
-	if (sk->sk_state == LLCP_CONNECTED)
+	if (sk->sk_state == LLCP_CONNECTED ||
+	    sk->sk_state == LLCP_CONNECTING)
 		nfc_put_device(llcp_sock->dev);
 
 	skb_queue_purge(&sk->sk_receive_queue);
-- 
2.43.0


^ permalink raw reply related

* Re: [PATCH] dt-bindings: net: convert microchip,lan78xx.txt to YAML schema
From: Rob Herring (Arm) @ 2026-07-07 18:32 UTC (permalink / raw)
  To: Mikhail Lukianchikov
  Cc: UNGLinuxDriver, Conor Dooley, devicetree, netdev,
	David S . Miller, Eric Dumazet, Krzysztof Kozlowski, linux-kernel,
	Rengarajan Sundararajan, Paolo Abeni, Jakub Kicinski, Andrew Lunn
In-Reply-To: <20260707165840.107409-1-avermoal@gmail.com>


On Tue, 07 Jul 2026 22:58:40 +0600, Mikhail Lukianchikov wrote:
> Convert the Microchip LAN78xx family (LAN7800, LAN7801, LAN7850) binding
> documentation from plain text to DT schema format using YAML.
> 
> The conversion was validated with 'make dt_binding_check'
> 
> Signed-off-by: Mikhail Lukianchikov <avermoal@gmail.com>
> ---
>  .../bindings/net/microchip,lan78xx.txt        |  53 --------
>  .../bindings/net/microchip,lan78xx.yaml       | 113 ++++++++++++++++++
>  2 files changed, 113 insertions(+), 53 deletions(-)
>  delete mode 100644 Documentation/devicetree/bindings/net/microchip,lan78xx.txt
>  create mode 100644 Documentation/devicetree/bindings/net/microchip,lan78xx.yaml
> 

My bot found errors running 'make dt_binding_check' on your patch:

yamllint warnings/errors:

dtschema/dtc warnings/errors:
/builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/net/microchip,lan78xx.example.dtb: /: 'compatible' is a required property
	from schema $id: http://devicetree.org/schemas/root-node.yaml
/builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/net/microchip,lan78xx.example.dtb: /: 'model' is a required property
	from schema $id: http://devicetree.org/schemas/root-node.yaml
/builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/net/microchip,lan78xx.example.dtb: /: '#address-cells' is a required property
	from schema $id: http://devicetree.org/schemas/root-node.yaml
/builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/net/microchip,lan78xx.example.dtb: /: '#size-cells' is a required property
	from schema $id: http://devicetree.org/schemas/root-node.yaml
Documentation/devicetree/bindings/net/microchip,lan78xx.example.dtb: /usb: failed to match any schema with compatible: ['usb-host']

doc reference errors (make refcheckdocs):
Warning: MAINTAINERS references a file that doesn't exist: Documentation/devicetree/bindings/net/microchip,lan78xx.txt
MAINTAINERS: Documentation/devicetree/bindings/net/microchip,lan78xx.txt

See https://patchwork.kernel.org/project/devicetree/patch/20260707165840.107409-1-avermoal@gmail.com

The base for the series is generally the latest rc1. A different dependency
should be noted in *this* patch.

If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure 'yamllint' is installed and dt-schema is up to
date:

pip3 install dtschema --upgrade

Please check and re-submit after running the above command yourself. Note
that DT_SCHEMA_FILES can be set to your schema file to speed up checking
your schema. However, it must be unset to test all examples with your schema.


^ permalink raw reply

* Re: [PATCH net] dibs: loopback: validate offset and size in move_data()
From: Andrew Lunn @ 2026-07-07 18:31 UTC (permalink / raw)
  To: Dust Li
  Cc: Alexandra Winter, Wenjia Zhang, Wen Gu, Paolo Abeni,
	Mahanta Jambigi, D . Wythe, Sidraya Jayagond, netdev,
	linux-kernel, stable, Federico Kirschbaum
In-Reply-To: <ak0NpKUDkvrkuSOm@linux.alibaba.com>

On Tue, Jul 07, 2026 at 10:31:00PM +0800, Dust Li wrote:
> On 2026-07-07 15:43:18, Dust Li wrote:
> >The loopback move_data() performs a memcpy into the registered DMB
> >without checking whether offset + size exceeds the DMB length.  Unlike
> >real ISM hardware, which enforces memory region bounds natively, the
> >software loopback has no such protection.
> >
> >A peer-supplied out-of-bounds offset or oversized write would result in
> >an OOB write past the allocated kernel buffer.  Add an explicit bounds
> >check before the memcpy to reject such requests with -EINVAL.
> >
> >Fixes: f7a22071dbf3("net/smc: implement DMB-related operations of loopback-ism")
> >Cc: stable@vger.kernel.org
> >Reported-by: Federico Kirschbaum <federico.kirschbaum@xbow.com>
> 
> Reported-by: Baul Lee <baul.lee@xbow.com>

Could you provide a link to the report?

Thanks
	Andrew

^ permalink raw reply

* Re: [PATCH net-next 00/15] net/mlx5e: PSP cleanups and improvements
From: Daniel Zahka @ 2026-07-07 18:29 UTC (permalink / raw)
  To: Tariq Toukan, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, netdev, Paolo Abeni
  Cc: Aleksandr Loktionov, Boris Pismenny, Chris Mi, Cosmin Ratiu,
	Dragos Tatulea, Gal Pressman, Jacob Keller, Jianbo Liu,
	Lama Kayal, Leon Romanovsky, linux-kernel, linux-rdma, Mark Bloch,
	Raed Salem, Rahul Rameshbabu, Saeed Mahameed, Stanislav Fomichev,
	Stanislav Fomichev, Willem de Bruijn
In-Reply-To: <20260707130858.969928-1-tariqt@nvidia.com>


On 7/7/26 9:08 AM, Tariq Toukan wrote:
> Hi,
>
> This series by Cosmin refactors mlx5 PSP support in preparation for
> HW-GRO support.
> There are almost no functionality changes in all but the last two
> patches, which address a long-standing TODO in mlx5e_psp_set_config().
>
> Regards,
> Tariq
>
> Cosmin Ratiu (15):
>    net/mlx5e: psp: Rename the saved psp_dev to 'psd'
>    net/mlx5e: psp: Remove PSP steering mutexes
>    net/mlx5e: psp: Remove unneeded ref counting for PSP steering
>    net/mlx5e: psp: Merge rx_err rule add/delete with ft create/delete
>    net/mlx5e: psp: Use helpers for steering object manipulation
>    net/mlx5e: psp: Factor out drop rule creation code
>    net/mlx5e: psp: Remove unused PSP syndrome copy action
>    net/mlx5e: psp: Rename and consolidate steering functions
>    net/mlx5e: psp: Adjust rx_check FT size and use a drop_group
>    net/mlx5e: psp: Add an RX steering table
>    net/mlx5e: psp: Use a single rx_check table
>    net/mlx5e: psp: Flatten steering structures
>    net/mlx5e: psp: Make PSP steering config dynamic
>    net/mlx5e: Return errors from profile->enable
>    net/mlx5e: psp: Report PSP dev registration errors
>
>   drivers/net/ethernet/mellanox/mlx5/core/en.h  |    2 +-
>   .../net/ethernet/mellanox/mlx5/core/en/fs.h   |    7 +-
>   .../mellanox/mlx5/core/en_accel/en_accel.h    |   19 +-
>   .../mellanox/mlx5/core/en_accel/psp.c         | 1007 ++++++++---------
>   .../mellanox/mlx5/core/en_accel/psp.h         |   18 +-
>   .../mellanox/mlx5/core/en_accel/psp_rxtx.c    |   13 +-
>   .../mellanox/mlx5/core/en_accel/psp_rxtx.h    |    3 +-
>   .../net/ethernet/mellanox/mlx5/core/en_main.c |   23 +-
>   .../net/ethernet/mellanox/mlx5/core/en_rep.c  |    8 +-
>   9 files changed, 516 insertions(+), 584 deletions(-)
>
>
> base-commit: 31816fc5d9acf8cdf226cdd0dc296e8cf15cc033

Thanks. Excited about the support for mlx5e_psp_set_config(). Jakub and 
I had a test case for psp_dev_ops::set_config() that we were waiting to 
upstream. I just rebased it onto net-next here: 
https://github.com/danieldzahka/linux/commit/b58e9a99573cf6b884e5fe3227c9af7a1f0d80b0

I ran it with the series but am seeing an error trying to catch 
undecrypted PSP-UDP packets after disabling all versions with set_config()

TAP version 13
1..30
ok 1 psp.data_basic_send.v0_ip4 # SKIP Test requires IPv4 connectivity
ok 2 psp.data_basic_send.v0_ip6
ok 3 psp.data_basic_send.v1_ip4 # SKIP Test requires IPv4 connectivity
ok 4 psp.data_basic_send.v1_ip6
ok 5 psp.data_basic_send.v2_ip4 # SKIP Test requires IPv4 connectivity
ok 6 psp.data_basic_send.v2_ip6 # SKIP ('PSP version not supported', 
'hdr0-aes-gmac-128')
ok 7 psp.data_basic_send.v3_ip4 # SKIP Test requires IPv4 connectivity
ok 8 psp.data_basic_send.v3_ip6 # SKIP ('PSP version not supported', 
'hdr0-aes-gmac-256')
ok 9 psp.data_mss_adjust.ip4 # SKIP Test requires IPv4 connectivity
ok 10 psp.data_mss_adjust.ip6
ok 11 psp.data_send_off.ip4 # SKIP Test requires IPv4 connectivity
# Exception| Traceback (most recent call last):
# Exception|   File "/root/ksft-psp-set-config/net/lib/py/ksft.py", line 
420, in ksft_run
# Exception|     func(*args)
# Exception|   File "/root/./ksft-psp-set-config/drivers/net/psp.py", 
line 608, in data_send_off
# Exception|     udps.recv(8192, socket.MSG_DONTWAIT)
# Exception| BlockingIOError: [Errno 11] Resource temporarily unavailable
# Exception|
not ok 12 psp.data_send_off.ip6
ok 13 psp.dev_list_devices
ok 14 psp.dev_get_device
ok 15 psp.dev_get_device_bad
ok 16 psp.dev_rotate
ok 17 psp.dev_rotate_spi
ok 18 psp.assoc_basic
ok 19 psp.assoc_bad_dev
ok 20 psp.assoc_sk_only_conn
ok 21 psp.assoc_sk_only_mismatch
ok 22 psp.assoc_sk_only_mismatch_tx
ok 23 psp.assoc_sk_only_unconn
ok 24 psp.assoc_version_mismatch
ok 25 psp.assoc_twice
ok 26 psp.data_send_bad_key
ok 27 psp.data_send_disconnect
ok 28 psp.data_stale_key
ok 29 psp.removal_device_rx # XFAIL Test only works on netdevsim
ok 30 psp.removal_device_bi # XFAIL Test only works on netdevsim
# Totals: pass:19 fail:1 xfail:2 xpass:0 skip:8 error:0
#
# Responder logs (0):
# STDERR:
# #  Set PSP enable on device 1 to 0x3
# #  Set PSP enable on device 1 to 0x0

I recall this working on an earlier prototype of this feature for mlx5. 
Are the steering rules setup to drop PSP-UDP packets when the 
corresponding psp version is disabled?


^ permalink raw reply

* Re: [PATCH bpf-next v6 2/3] bpf: Add BPF_FIB_LOOKUP_VLAN_INPUT flag to bpf_fib_lookup() helper
From: Emil Tsalapatis @ 2026-07-07 18:27 UTC (permalink / raw)
  To: Avinash Duduskar, ast, daniel, andrii
  Cc: eddyz87, memxor, martin.lau, song, yonghong.song, jolsa, emil,
	john.fastabend, sdf, davem, edumazet, kuba, pabeni, horms, shuah,
	hawk, yatsenko, leon.hwang, kpsingh, a.s.protopopov, ameryhung,
	rongtao, eyal.birger, bpf, netdev, linux-kernel, linux-kselftest,
	toke, dsahern
In-Reply-To: <20260704092159.1256823-3-avinash.duduskar@gmail.com>

On Sat Jul 4, 2026 at 5:21 AM EDT, Avinash Duduskar wrote:
> BPF_FIB_LOOKUP_VLAN resolves a VLAN egress. The reverse is also
> useful: an XDP program receiving a VLAN-tagged frame on a physical
> device wants the lookup to behave as if the packet had arrived on the
> corresponding VLAN subinterface, so iif-based policy routing and VRF
> table selection use the right ingress.
>
> Add BPF_FIB_LOOKUP_VLAN_INPUT. When set, params->h_vlan_proto and
> params->h_vlan_TCI are read as an input VLAN tag and the matching VLAN
> device of params->ifindex is resolved with __vlan_find_dev_deep_rcu().
> The device must be up and in the same network namespace as
> params->ifindex (a VLAN device can be moved to another netns while
> registered on its parent; receive would deliver into that other
> namespace, which a lookup here cannot represent). If params->ifindex
> is itself a VLAN device, its inner (QinQ) subinterface is matched.
> For a bond or team, a tag on a port matches no device and returns
> NOT_FWDED; pass the master's ifindex.
> The lookup then runs with the resolved device as the ingress;
> params->ifindex itself is not modified on the input side. When the
> resolved device is enslaved to a VRF, both the full lookup (via the
> l3mdev rule) and BPF_FIB_LOOKUP_DIRECT (via l3mdev_fib_table_rcu())
> select the VRF's table from the resolved ingress. That follows from
> feeding the resolved device to the flow as the ingress
> (fl4.flowi4_iif = dev->ifindex), which is what makes l3mdev resolve
> the VRF master from the subinterface rather than from
> params->ifindex.
>
> The two failure classes get different treatment on purpose. A
> h_vlan_proto other than 802.1Q/802.1ad is API misuse and returns
> -EINVAL, since it would otherwise reach the WARN in vlan_proto_idx()
> with a program-controlled value. An unmatched VID, a device that is
> down, or one in another namespace is a data outcome and returns
> BPF_FIB_LKUP_RET_NOT_FWDED, matching the DIRECT path when
> fib_get_table() finds no table and mirroring real ingress, where the
> receive path drops such frames. A VID of 0 (a priority tag) is looked
> up literally and normally fails the same way; receive instead
> processes such frames untagged, so callers should not set the flag for
> priority tags. Proceeding on the physical device for any of these
> would be fail-open for the policy-routing cases above.
>
> The h_vlan fields share a union with tbid, so the flag cannot be
> combined with BPF_FIB_LOOKUP_TBID. It describes ingress, so it also
> cannot be combined with BPF_FIB_LOOKUP_OUTPUT. Both combinations
> return -EINVAL; restricting now keeps a later relaxation backward
> compatible. Combining with BPF_FIB_LOOKUP_VLAN is allowed: the tag is
> consumed on the ingress side and the egress tag is written on
> success.
>
> Under !CONFIG_VLAN_8021Q the __vlan_find_dev_deep_rcu() stub returns
> NULL, so every lookup with the flag returns NOT_FWDED, which is
> correct since no VLAN device can exist.
>
> Suggested-by: Toke Høiland-Jørgensen <toke@redhat.com>
> Reviewed-by: Toke Høiland-Jørgensen <toke@redhat.com>
> Signed-off-by: Avinash Duduskar <avinash.duduskar@gmail.com>

Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>

> ---
>  include/uapi/linux/bpf.h       | 21 ++++++++++-
>  net/core/filter.c              | 66 +++++++++++++++++++++++++++++++---
>  tools/include/uapi/linux/bpf.h | 21 ++++++++++-
>  3 files changed, 101 insertions(+), 7 deletions(-)
>
> diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
> index e00f0392e728..d4218954c50f 100644
> --- a/include/uapi/linux/bpf.h
> +++ b/include/uapi/linux/bpf.h
> @@ -3555,6 +3555,22 @@ union bpf_attr {
>   *			This flag is only valid for XDP programs; tc programs
>   *			receive -EINVAL since they can redirect to the VLAN
>   *			device directly.
> + *		**BPF_FIB_LOOKUP_VLAN_INPUT**
> + *			Treat *params*->h_vlan_proto and *params*->h_vlan_TCI
> + *			as an input VLAN tag and run the lookup as if ingress
> + *			had happened on the VLAN subinterface carrying that tag
> + *			on *params*->ifindex. The VID is the low 12 bits of
> + *			*params*->h_vlan_TCI; *params*->h_vlan_proto must be
> + *			ETH_P_8021Q or ETH_P_8021AD in network byte order, else
> + *			**-EINVAL**. If *params*->ifindex is itself a VLAN
> + *			device, its inner (QinQ) subinterface is matched; for a
> + *			bond or team, pass the master's ifindex. An unmatched
> + *			tag, a down device, or one in another namespace returns
> + *			**BPF_FIB_LKUP_RET_NOT_FWDED**, mirroring real ingress.
> + *			A VID of 0 is looked up literally, so do not set this
> + *			flag for priority-tagged frames. Cannot be combined with
> + *			**BPF_FIB_LOOKUP_TBID** or **BPF_FIB_LOOKUP_OUTPUT**
> + *			(returns **-EINVAL**).
>   *
>   *		*ctx* is either **struct xdp_md** for XDP programs or
>   *		**struct sk_buff** tc cls_act programs.
> @@ -7351,6 +7367,7 @@ enum {
>  	BPF_FIB_LOOKUP_SRC     = (1U << 4),
>  	BPF_FIB_LOOKUP_MARK    = (1U << 5),
>  	BPF_FIB_LOOKUP_VLAN    = (1U << 6),
> +	BPF_FIB_LOOKUP_VLAN_INPUT = (1U << 7),
>  };
>  
>  enum {
> @@ -7421,7 +7438,9 @@ struct bpf_fib_lookup {
>  			/*
>  			 * output with BPF_FIB_LOOKUP_VLAN: set from the
>  			 * resolved egress VLAN device (see the flag); zeroed
> -			 * on other successful lookups.
> +			 * on other successful lookups. input with
> +			 * BPF_FIB_LOOKUP_VLAN_INPUT: the VLAN tag to scope
> +			 * the lookup by.
>  			 */
>  			__be16	h_vlan_proto;
>  			__be16	h_vlan_TCI;
> diff --git a/net/core/filter.c b/net/core/filter.c
> index b5a45485a54b..0ea362fa4287 100644
> --- a/net/core/filter.c
> +++ b/net/core/filter.c
> @@ -6229,6 +6229,25 @@ static int bpf_fib_set_fwd_params(struct net_device *dev,
>  
>  	return 0;
>  }
> +
> +static struct net_device *bpf_fib_vlan_input_dev(struct net_device *dev,
> +						 const struct bpf_fib_lookup *params)
> +{
> +	__be16 proto = params->h_vlan_proto;
> +	struct net_device *vlan_dev;
> +	u16 vid;
> +
> +	if (proto != htons(ETH_P_8021Q) && proto != htons(ETH_P_8021AD))
> +		return ERR_PTR(-EINVAL);
> +
> +	vid = ntohs(params->h_vlan_TCI) & VLAN_VID_MASK;
> +	vlan_dev = __vlan_find_dev_deep_rcu(dev, proto, vid);
> +	if (!vlan_dev || !(vlan_dev->flags & IFF_UP) ||
> +	    !net_eq(dev_net(vlan_dev), dev_net(dev)))
> +		return NULL;
> +
> +	return vlan_dev;
> +}
>  #endif
>  
>  #if IS_ENABLED(CONFIG_INET)
> @@ -6249,6 +6268,14 @@ static int bpf_ipv4_fib_lookup(struct net *net, struct bpf_fib_lookup *params,
>  	if (unlikely(!dev))
>  		return -ENODEV;
>  
> +	if (flags & BPF_FIB_LOOKUP_VLAN_INPUT) {
> +		dev = bpf_fib_vlan_input_dev(dev, params);
> +		if (IS_ERR(dev))
> +			return PTR_ERR(dev);
> +		if (!dev)
> +			return BPF_FIB_LKUP_RET_NOT_FWDED;
> +	}
> +
>  	/* verify forwarding is enabled on this interface */
>  	in_dev = __in_dev_get_rcu(dev);
>  	if (unlikely(!in_dev || !IN_DEV_FORWARD(in_dev)))
> @@ -6258,7 +6285,11 @@ static int bpf_ipv4_fib_lookup(struct net *net, struct bpf_fib_lookup *params,
>  		fl4.flowi4_iif = 1;
>  		fl4.flowi4_oif = params->ifindex;
>  	} else {
> -		fl4.flowi4_iif = params->ifindex;
> +		/*
> +		 * dev->ifindex, not params->ifindex: VLAN_INPUT may have
> +		 * resolved dev to a subinterface above.
> +		 */
> +		fl4.flowi4_iif = dev->ifindex;
>  		fl4.flowi4_oif = 0;
>  	}
>  	fl4.flowi4_dscp = inet_dsfield_to_dscp(params->tos);
> @@ -6395,6 +6426,14 @@ static int bpf_ipv6_fib_lookup(struct net *net, struct bpf_fib_lookup *params,
>  	if (unlikely(!dev))
>  		return -ENODEV;
>  
> +	if (flags & BPF_FIB_LOOKUP_VLAN_INPUT) {
> +		dev = bpf_fib_vlan_input_dev(dev, params);
> +		if (IS_ERR(dev))
> +			return PTR_ERR(dev);
> +		if (!dev)
> +			return BPF_FIB_LKUP_RET_NOT_FWDED;
> +	}
> +
>  	idev = __in6_dev_get_safely(dev);
>  	if (unlikely(!idev || !READ_ONCE(idev->cnf.forwarding)))
>  		return BPF_FIB_LKUP_RET_FWD_DISABLED;
> @@ -6403,7 +6442,12 @@ static int bpf_ipv6_fib_lookup(struct net *net, struct bpf_fib_lookup *params,
>  		fl6.flowi6_iif = 1;
>  		oif = fl6.flowi6_oif = params->ifindex;
>  	} else {
> -		oif = fl6.flowi6_iif = params->ifindex;
> +		/*
> +		 * dev->ifindex, not params->ifindex: VLAN_INPUT may have
> +		 * resolved dev to a subinterface above.
> +		 */
> +		oif = dev->ifindex;
> +		fl6.flowi6_iif = oif;
>  		fl6.flowi6_oif = 0;
>  		strict = RT6_LOOKUP_F_HAS_SADDR;
>  	}
> @@ -6514,7 +6558,19 @@ static int bpf_ipv6_fib_lookup(struct net *net, struct bpf_fib_lookup *params,
>  #define BPF_FIB_LOOKUP_MASK (BPF_FIB_LOOKUP_DIRECT | BPF_FIB_LOOKUP_OUTPUT | \
>  			     BPF_FIB_LOOKUP_SKIP_NEIGH | BPF_FIB_LOOKUP_TBID | \
>  			     BPF_FIB_LOOKUP_SRC | BPF_FIB_LOOKUP_MARK | \
> -			     BPF_FIB_LOOKUP_VLAN)
> +			     BPF_FIB_LOOKUP_VLAN | BPF_FIB_LOOKUP_VLAN_INPUT)
> +
> +static bool bpf_fib_lookup_flags_ok(u32 flags)
> +{
> +	if (flags & ~BPF_FIB_LOOKUP_MASK)
> +		return false;
> +
> +	if ((flags & BPF_FIB_LOOKUP_VLAN_INPUT) &&
> +	    (flags & (BPF_FIB_LOOKUP_TBID | BPF_FIB_LOOKUP_OUTPUT)))
> +		return false;
> +
> +	return true;
> +}
>  
>  BPF_CALL_4(bpf_xdp_fib_lookup, struct xdp_buff *, ctx,
>  	   struct bpf_fib_lookup *, params, int, plen, u32, flags)
> @@ -6522,7 +6578,7 @@ BPF_CALL_4(bpf_xdp_fib_lookup, struct xdp_buff *, ctx,
>  	if (plen < sizeof(*params))
>  		return -EINVAL;
>  
> -	if (flags & ~BPF_FIB_LOOKUP_MASK)
> +	if (!bpf_fib_lookup_flags_ok(flags))
>  		return -EINVAL;
>  
>  	switch (params->family) {
> @@ -6560,7 +6616,7 @@ BPF_CALL_4(bpf_skb_fib_lookup, struct sk_buff *, skb,
>  	if (plen < sizeof(*params))
>  		return -EINVAL;
>  
> -	if (flags & ~BPF_FIB_LOOKUP_MASK)
> +	if (!bpf_fib_lookup_flags_ok(flags))
>  		return -EINVAL;
>  
>  	if (flags & BPF_FIB_LOOKUP_VLAN)
> diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h
> index e00f0392e728..d4218954c50f 100644
> --- a/tools/include/uapi/linux/bpf.h
> +++ b/tools/include/uapi/linux/bpf.h
> @@ -3555,6 +3555,22 @@ union bpf_attr {
>   *			This flag is only valid for XDP programs; tc programs
>   *			receive -EINVAL since they can redirect to the VLAN
>   *			device directly.
> + *		**BPF_FIB_LOOKUP_VLAN_INPUT**
> + *			Treat *params*->h_vlan_proto and *params*->h_vlan_TCI
> + *			as an input VLAN tag and run the lookup as if ingress
> + *			had happened on the VLAN subinterface carrying that tag
> + *			on *params*->ifindex. The VID is the low 12 bits of
> + *			*params*->h_vlan_TCI; *params*->h_vlan_proto must be
> + *			ETH_P_8021Q or ETH_P_8021AD in network byte order, else
> + *			**-EINVAL**. If *params*->ifindex is itself a VLAN
> + *			device, its inner (QinQ) subinterface is matched; for a
> + *			bond or team, pass the master's ifindex. An unmatched
> + *			tag, a down device, or one in another namespace returns
> + *			**BPF_FIB_LKUP_RET_NOT_FWDED**, mirroring real ingress.
> + *			A VID of 0 is looked up literally, so do not set this
> + *			flag for priority-tagged frames. Cannot be combined with
> + *			**BPF_FIB_LOOKUP_TBID** or **BPF_FIB_LOOKUP_OUTPUT**
> + *			(returns **-EINVAL**).
>   *
>   *		*ctx* is either **struct xdp_md** for XDP programs or
>   *		**struct sk_buff** tc cls_act programs.
> @@ -7351,6 +7367,7 @@ enum {
>  	BPF_FIB_LOOKUP_SRC     = (1U << 4),
>  	BPF_FIB_LOOKUP_MARK    = (1U << 5),
>  	BPF_FIB_LOOKUP_VLAN    = (1U << 6),
> +	BPF_FIB_LOOKUP_VLAN_INPUT = (1U << 7),
>  };
>  
>  enum {
> @@ -7421,7 +7438,9 @@ struct bpf_fib_lookup {
>  			/*
>  			 * output with BPF_FIB_LOOKUP_VLAN: set from the
>  			 * resolved egress VLAN device (see the flag); zeroed
> -			 * on other successful lookups.
> +			 * on other successful lookups. input with
> +			 * BPF_FIB_LOOKUP_VLAN_INPUT: the VLAN tag to scope
> +			 * the lookup by.
>  			 */
>  			__be16	h_vlan_proto;
>  			__be16	h_vlan_TCI;


^ permalink raw reply

* Re: [PATCH ipsec] xfrm6: clear dst.dev on error to avoid double netdev_put in xfrm6_fill_dst()
From: Xiang Mei @ 2026-07-07 18:18 UTC (permalink / raw)
  To: Steffen Klassert
  Cc: Herbert Xu, David S . Miller, netdev, Simon Horman, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, AutonomousCodeSecurity, tgopinath,
	kys
In-Reply-To: <akyd7YVSczkMzK-h@secunet.com>

On Mon, Jul 6, 2026 at 11:34 PM Steffen Klassert
<steffen.klassert@secunet.com> wrote:
>
> On Thu, Jul 02, 2026 at 01:05:16AM +0000, Xiang Mei (Microsoft) wrote:
> > On the error path where in6_dev_get(dev) returns NULL, xfrm6_fill_dst()
> > releases the device reference with netdev_put() but leaves
> > xdst->u.dst.dev set. dst_destroy() later calls netdev_put(dst->dev)
> > again, so the same net_device reference is released twice, underflowing
> > its refcount (ref_tracker WARNING + "unregister_netdevice: waiting for
> > <dev> to become free").
> >
> > Clear xdst->u.dst.dev after the netdev_put(), the same way the XFRM
> > device-offload paths xfrm_dev_state_add() and xfrm_dev_policy_add() in
> > net/xfrm/xfrm_device.c NULL ->dev when releasing the reference on error.
> >
> >   ref_tracker: reference already released.
> >   ref_tracker: allocated in:
> >    xfrm6_fill_dst (net/ipv6/xfrm6_policy.c:86)
> >    ...
> >    udpv6_sendmsg (net/ipv6/udp.c:1696)
> >    ...
> >   ref_tracker: freed in:
> >    xfrm6_fill_dst (net/ipv6/xfrm6_policy.c:90)
> >    ...
> >   WARNING: lib/ref_tracker.c:322 at ref_tracker_free+0x58b/0x780
> >    dst_destroy (net/core/dst.c:115)
> >    rcu_core
> >    handle_softirqs
> >    ...
> >
> > Fixes: 84c4a9dfbf43 ("xfrm6: release dev before returning error")
> > Reported-by: AutonomousCodeSecurity@microsoft.com
> > Signed-off-by: Xiang Mei (Microsoft) <xmei5@asu.edu>
>
> Applied, thanks a lot!
Thank you!

^ permalink raw reply

* Re: [PATCH ipsec v2] xfrm: policy: preallocate inexact bins before xfrm_hash_rebuild reinsert
From: Xiang Mei @ 2026-07-07 18:13 UTC (permalink / raw)
  To: Steffen Klassert
  Cc: Florian Westphal, herbert, davem, netdev, horms, edumazet, kuba,
	pabeni, AutonomousCodeSecurity, tgopinath, kys
In-Reply-To: <akyeIoKtMaRiQUI7@secunet.com>

On Mon, Jul 6, 2026 at 11:35 PM Steffen Klassert
<steffen.klassert@secunet.com> wrote:
>
> On Fri, Jul 03, 2026 at 07:47:19AM +0200, Florian Westphal wrote:
> > Xiang Mei (Microsoft) <xmei5@asu.edu> wrote:
> > > xfrm_hash_rebuild()'s first loop preallocates the bins/chains the reinsert
> > > loop needs, so the reinsert (after hlist_del_rcu()) cannot allocate or
> > > fail. But its guard is inverted: it skips policies with prefixlen <
> > > threshold and preallocates for the rest.
> > >
> > > prefixlen < threshold is exactly when policy_hash_bysel() returns NULL and
> > > the reinsert takes the allocating xfrm_policy_inexact_insert() path. So the
> > > loop preallocates for the exact policies (which never allocate) and skips
> > > the inexact ones, whose bin/node is then allocated GFP_ATOMIC during
> > > reinsert. On failure the error path only WARN_ONCE()s and continues,
> > > leaving a poisoned bydst node; the next rebuild's hlist_del_rcu()
> > > dereferences LIST_POISON2 and takes a GPF. Reachable under memory pressure,
> > > deterministic via failslab.
> > >
> > > Invert the guard so preallocation covers exactly the reinserted policies;
> > > the reinsert then allocates nothing and cannot fail.
> > >
> > > Crash:
> > >   Oops: general protection fault, probably for non-canonical address
> > >   0xfbd59c0000000024: 0000 [#1] SMP KASAN NOPTI
> > >   KASAN: maybe wild-memory-access in range [0xdead...]
> > >   ...
> > >   Workqueue: events xfrm_hash_rebuild
> > >   RIP: 0010:xfrm_hash_rebuild+0x5b3/0x1190
> > >   RAX: dead000000000122   (LIST_POISON2 + offset)
> > >   ...
> > >   Call Trace:
> > >    hlist_del_rcu (include/linux/rculist.h:599)
> > >    xfrm_hash_rebuild (net/xfrm/xfrm_policy.c:1365)
> > >    process_one_work (kernel/workqueue.c:3322)
> > >    worker_thread (kernel/workqueue.c:3486)
> > >    kthread (kernel/kthread.c:436)
> > >    ret_from_fork (arch/x86/kernel/process.c:158)
> > >    ret_from_fork_asm (arch/x86/entry/entry_64.S:245)
> > >    ...
> > >   Kernel panic - not syncing: Fatal exception in interrupt
> > >
> > > Fixes: 24969facd704 ("xfrm: policy: store inexact policies in an rhashtable")
> > > Reported-by: AutonomousCodeSecurity@microsoft.com
> > > Signed-off-by: Xiang Mei (Microsoft) <xmei5@asu.edu>
> >
> > Reviewed-by: Florian Westphal <fw@strlen.de>
>
> Applied, thanks everyone!
Awesome, thanks!

^ permalink raw reply

* Re: [PATCH ipsec v2] xfrm: policy: preallocate inexact bins before xfrm_hash_rebuild reinsert
From: Xiang Mei @ 2026-07-07 18:13 UTC (permalink / raw)
  To: Manuel Ebner
  Cc: fw, steffen.klassert, herbert, davem, netdev, horms, edumazet,
	kuba, pabeni, AutonomousCodeSecurity, tgopinath, kys
In-Reply-To: <d6cfd7bd90001af215ba456ab81404a60fbea1c4.camel@mailbox.org>

On Thu, Jul 2, 2026 at 11:19 PM Manuel Ebner <manuelebner@mailbox.org> wrote:
>
> Hi Xiang Mei
>
> On Fri, 2026-07-03 at 05:19 +0000, Xiang Mei (Microsoft) wrote:
> > xfrm_hash_rebuild()'s first loop preallocates the bins/chains the reinsert
> > loop needs, so the reinsert (after hlist_del_rcu()) cannot allocate or
> > fail.
>
> This sentence is difficult to understand for me. Can it be simplified or changed?
> This would help a little:
>
> xfrm_hash_rebuild()'s first loop preallocates the bins/chains which the reinsert
> loop needs. In this case the reinsert ...
>
Sorry for the delayed reply; I was on some task in the last few days.
Also, thanks for your review; your rewording is clearer.
>
> > But its guard is inverted: it skips policies with prefixlen <
> > threshold and preallocates for the rest.
> >
> > prefixlen < threshold is exactly when policy_hash_bysel() returns NULL and
> > the reinsert takes the allocating xfrm_policy_inexact_insert() path. So the
> > loop preallocates for the exact policies (which never allocate) and skips
> > the inexact ones, whose bin/node is then allocated GFP_ATOMIC during
> > reinsert. On failure the error path only WARN_ONCE()s and continues,
> > leaving a poisoned bydst node;
>
> what's 'bydst'?
>

It's a variable/field name: policy->bydst, the hlist node linking each
policy into the destination-indexed policy hash.

> > the next rebuild's hlist_del_rcu()
> > dereferences LIST_POISON2 and takes a GPF.
>
> /GPF/GFP/
>
GPF here is general protection fault (the oops), not GFP_.* (the
allocation flag).

Since a maintainer has already applied it and these are wording issues, I
lean toward leaving it as-is unless you feel strongly.

Thanks,
Xiang
> Thanks
>  Manuel
>
> > Reachable under memory pressure,
> > deterministic via failslab.
> >
> > Invert the guard so preallocation covers exactly the reinserted policies;
> > the reinsert then allocates nothing and cannot fail.
> >
> > Crash:
> >   Oops: general protection fault, probably for non-canonical address
> >   0xfbd59c0000000024: 0000 [#1] SMP KASAN NOPTI
> >   KASAN: maybe wild-memory-access in range [0xdead...]
> >   ...
> >   Workqueue: events xfrm_hash_rebuild
> >   RIP: 0010:xfrm_hash_rebuild+0x5b3/0x1190
> >   RAX: dead000000000122   (LIST_POISON2 + offset)
> >   ...
> >   Call Trace:
> >    hlist_del_rcu (include/linux/rculist.h:599)
> >    xfrm_hash_rebuild (net/xfrm/xfrm_policy.c:1365)
> >    process_one_work (kernel/workqueue.c:3322)
> >    worker_thread (kernel/workqueue.c:3486)
> >    kthread (kernel/kthread.c:436)
> >    ret_from_fork (arch/x86/kernel/process.c:158)
> >    ret_from_fork_asm (arch/x86/entry/entry_64.S:245)
> >    ...
> >   Kernel panic - not syncing: Fatal exception in interrupt
> >
> > Fixes: 24969facd704 ("xfrm: policy: store inexact policies in an rhashtable")
> > Reported-by: AutonomousCodeSecurity@microsoft.com
> > Signed-off-by: Xiang Mei (Microsoft) <xmei5@asu.edu>
> > ---
> > v2: fix the inverted preallocation guard (root cause)
> >     instead of avoiding crash
> >
> >  net/xfrm/xfrm_policy.c | 4 ++--
> >  1 file changed, 2 insertions(+), 2 deletions(-)
> >
> > diff --git a/net/xfrm/xfrm_policy.c b/net/xfrm/xfrm_policy.c
> > index 7ef861a0e823..932a313b9460 100644
> > --- a/net/xfrm/xfrm_policy.c
> > +++ b/net/xfrm/xfrm_policy.c
> > @@ -1329,8 +1329,8 @@ static void xfrm_hash_rebuild(struct work_struct *work)
> >                       }
> >               }
> >
> > -             if (policy->selector.prefixlen_d < dbits ||
> > -                 policy->selector.prefixlen_s < sbits)
> > +             if (policy->selector.prefixlen_d >= dbits &&
> > +                 policy->selector.prefixlen_s >= sbits)
> >                       continue;
> >
> >               bin = xfrm_policy_inexact_alloc_bin(policy, dir);

^ permalink raw reply

* Re: [PATCH bpf-next v6 1/3] bpf: Add BPF_FIB_LOOKUP_VLAN flag to bpf_fib_lookup() helper
From: Emil Tsalapatis @ 2026-07-07 18:07 UTC (permalink / raw)
  To: Avinash Duduskar, ast, daniel, andrii
  Cc: eddyz87, memxor, martin.lau, song, yonghong.song, jolsa, emil,
	john.fastabend, sdf, davem, edumazet, kuba, pabeni, horms, shuah,
	hawk, yatsenko, leon.hwang, kpsingh, a.s.protopopov, ameryhung,
	rongtao, eyal.birger, bpf, netdev, linux-kernel, linux-kselftest,
	toke, dsahern
In-Reply-To: <20260704092159.1256823-2-avinash.duduskar@gmail.com>

On Sat Jul 4, 2026 at 5:21 AM EDT, Avinash Duduskar wrote:
> bpf_fib_lookup() returns the FIB-resolved egress ifindex straight
> from the fib result. When the egress is a VLAN device, the returned
> ifindex is the VLAN netdev's, which has no XDP xmit handler; XDP
> programs that want to forward the frame (e.g. xdp-forward) must
> instead target the underlying physical device and push the VLAN tag
> themselves. Today the program has no way to learn either the
> underlying ifindex or the VLAN tag without maintaining its own
> VLAN-to-ifindex map in userspace and refreshing it on netlink
> events.
>
> Add BPF_FIB_LOOKUP_VLAN. When the caller sets this flag and the fib
> result is a VLAN device whose immediate parent is a real (non-VLAN)
> device in the same network namespace, populate the existing output
> fields params->h_vlan_proto and params->h_vlan_TCI from the VLAN
> device and replace params->ifindex with the parent's ifindex.
> params->h_vlan_TCI carries the VID only, with PCP and DEI bits zero; a
> consumer wanting to set egress priority writes PCP itself.
> params->smac is the VLAN device's own address, which can differ from
> the parent's.
>
> Only the immediate parent is resolved, via vlan_dev_priv(dev)->real_dev
> and not vlan_dev_real_dev(), which walks to the bottom of a stack. When
> the immediate parent is not a real device in the same namespace, the
> lookup returns BPF_FIB_LKUP_RET_VLAN_FAILURE and leaves params->ifindex
> at the input. This covers a stacked VLAN (QinQ), where the immediate
> parent is itself a VLAN device and one h_vlan_proto/h_vlan_TCI pair
> cannot describe two tags, and a parent in another network namespace (a
> VLAN device can be moved while its parent stays), whose ifindex would
> be meaningless in the caller's namespace. A program that wants the VLAN
> device's own ifindex re-issues the lookup without BPF_FIB_LOOKUP_VLAN,
> so the unreducible case stays distinct from a physical egress. That
> distinction matters for XDP: a program cannot xmit on a VLAN device, so
> a success carrying the VLAN ifindex would make it redirect to a device
> with no ndo_xdp_xmit and drop the frame at xdp_do_flush(). The swap and
> the vlan fields are written only on the reduce path; other output
> fields keep their existing behaviour, so a frag-needed result still
> reports the route mtu in params->mtu_result.
>
> BPF_FIB_LOOKUP_VLAN is only useful to XDP, which cannot redirect to a
> VLAN device. A tc program can redirect to the VLAN device directly, so
> bpf_skb_fib_lookup() rejects the flag with -EINVAL; bpf_xdp_fib_lookup()
> accepts it. When the flag is not set, behaviour is unchanged:
> h_vlan_proto and h_vlan_TCI are zeroed and ifindex is left at the FIB
> result.
>
> The new block is compiled only under CONFIG_VLAN_8021Q since
> vlan_dev_priv() is not defined otherwise; without that config
> is_vlan_dev() is constant false and the flag is accepted but never
> acts. That is safe because no VLAN device can exist there, so every
> egress is already physical.
>
> This lets an XDP redirect target the physical device and learn the
> tag to push in a single lookup, which xdp-forward's optional VLAN
> mode (xdp-project/xdp-tools#504) wants from the kernel side.
>
> The helper's input semantics are unchanged; the reverse direction
> (supplying a tag as lookup input) is added in the following patch.
>
> Suggested-by: Toke Høiland-Jørgensen <toke@redhat.com>
> Reviewed-by: Toke Høiland-Jørgensen <toke@redhat.com>
> Acked-by: David Ahern <dsahern@kernel.org>
> Signed-off-by: Avinash Duduskar <avinash.duduskar@gmail.com>

Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>

> ---
>  include/uapi/linux/bpf.h       | 31 ++++++++++++++++++++++++++++++-
>  net/core/filter.c              | 33 +++++++++++++++++++++++++++++----
>  tools/include/uapi/linux/bpf.h | 31 ++++++++++++++++++++++++++++++-
>  3 files changed, 89 insertions(+), 6 deletions(-)
>
> diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
> index 89b36de5fdbb..e00f0392e728 100644
> --- a/include/uapi/linux/bpf.h
> +++ b/include/uapi/linux/bpf.h
> @@ -3532,6 +3532,29 @@ union bpf_attr {
>   *			Use the mark present in *params*->mark for the fib lookup.
>   *			This option should not be used with BPF_FIB_LOOKUP_DIRECT,
>   *			as it only has meaning for full lookups.
> + *		**BPF_FIB_LOOKUP_VLAN**
> + *			If the fib lookup resolves to a VLAN device whose
> + *			parent is a real (non-VLAN) device, set
> + *			*params*->h_vlan_proto and *params*->h_vlan_TCI from
> + *			the VLAN device and replace *params*->ifindex with the
> + *			parent's ifindex. *params*->h_vlan_TCI carries the VID
> + *			only, with PCP and DEI bits zero; a consumer wanting to
> + *			set egress priority writes PCP itself. *params*->smac is
> + *			the VLAN device's own address, which can differ from the
> + *			parent's. Only the immediate parent is resolved; if it
> + *			is itself a VLAN device (QinQ) or in another namespace,
> + *			the egress cannot be reduced to a physical device plus
> + *			one tag and the lookup returns
> + *			**BPF_FIB_LKUP_RET_VLAN_FAILURE** with *params*->ifindex
> + *			left at the input. Re-issue without
> + *			**BPF_FIB_LOOKUP_VLAN** to obtain the VLAN device's own
> + *			ifindex. The swap and the vlan fields
> + *			are written only on success; other output fields keep
> + *			the helper's existing behaviour, so a frag-needed result
> + *			still reports the route mtu in *params*->mtu_result.
> + *			This flag is only valid for XDP programs; tc programs
> + *			receive -EINVAL since they can redirect to the VLAN
> + *			device directly.
>   *
>   *		*ctx* is either **struct xdp_md** for XDP programs or
>   *		**struct sk_buff** tc cls_act programs.
> @@ -7327,6 +7350,7 @@ enum {
>  	BPF_FIB_LOOKUP_TBID    = (1U << 3),
>  	BPF_FIB_LOOKUP_SRC     = (1U << 4),
>  	BPF_FIB_LOOKUP_MARK    = (1U << 5),
> +	BPF_FIB_LOOKUP_VLAN    = (1U << 6),
>  };
>  
>  enum {
> @@ -7340,6 +7364,7 @@ enum {
>  	BPF_FIB_LKUP_RET_NO_NEIGH,     /* no neighbor entry for nh */
>  	BPF_FIB_LKUP_RET_FRAG_NEEDED,  /* fragmentation required to fwd */
>  	BPF_FIB_LKUP_RET_NO_SRC_ADDR,  /* failed to derive IP src addr */
> +	BPF_FIB_LKUP_RET_VLAN_FAILURE, /* VLAN egress, parent unresolvable */
>  };
>  
>  struct bpf_fib_lookup {
> @@ -7393,7 +7418,11 @@ struct bpf_fib_lookup {
>  
>  	union {
>  		struct {
> -			/* output */
> +			/*
> +			 * output with BPF_FIB_LOOKUP_VLAN: set from the
> +			 * resolved egress VLAN device (see the flag); zeroed
> +			 * on other successful lookups.
> +			 */
>  			__be16	h_vlan_proto;
>  			__be16	h_vlan_TCI;
>  		};
> diff --git a/net/core/filter.c b/net/core/filter.c
> index 2e96b4b847ce..b5a45485a54b 100644
> --- a/net/core/filter.c
> +++ b/net/core/filter.c
> @@ -6201,10 +6201,29 @@ static const struct bpf_func_proto bpf_skb_get_xfrm_state_proto = {
>  #endif
>  
>  #if IS_ENABLED(CONFIG_INET) || IS_ENABLED(CONFIG_IPV6)
> -static int bpf_fib_set_fwd_params(struct bpf_fib_lookup *params, u32 mtu)
> +static int bpf_fib_set_fwd_params(struct net_device *dev,
> +				  struct bpf_fib_lookup *params,
> +				  u32 flags, u32 mtu, u32 in_ifindex)
>  {
>  	params->h_vlan_TCI = 0;
>  	params->h_vlan_proto = 0;
> +
> +#if IS_ENABLED(CONFIG_VLAN_8021Q)
> +	if ((flags & BPF_FIB_LOOKUP_VLAN) && is_vlan_dev(dev)) {
> +		struct net_device *real_dev = vlan_dev_priv(dev)->real_dev;
> +
> +		if (!is_vlan_dev(real_dev) &&
> +		    net_eq(dev_net(real_dev), dev_net(dev))) {
> +			params->h_vlan_proto = vlan_dev_vlan_proto(dev);
> +			params->h_vlan_TCI = htons(vlan_dev_vlan_id(dev));
> +			params->ifindex = real_dev->ifindex;
> +		} else {
> +			params->ifindex = in_ifindex;
> +			return BPF_FIB_LKUP_RET_VLAN_FAILURE;
> +		}
> +	}
> +#endif
> +
>  	if (mtu)
>  		params->mtu_result = mtu; /* union with tot_len */
>  
> @@ -6216,6 +6235,7 @@ static int bpf_fib_set_fwd_params(struct bpf_fib_lookup *params, u32 mtu)
>  static int bpf_ipv4_fib_lookup(struct net *net, struct bpf_fib_lookup *params,
>  			       u32 flags, bool check_mtu)
>  {
> +	u32 in_ifindex = params->ifindex;
>  	struct neighbour *neigh = NULL;
>  	struct fib_nh_common *nhc;
>  	struct in_device *in_dev;
> @@ -6347,7 +6367,7 @@ static int bpf_ipv4_fib_lookup(struct net *net, struct bpf_fib_lookup *params,
>  	memcpy(params->smac, dev->dev_addr, ETH_ALEN);
>  
>  set_fwd_params:
> -	return bpf_fib_set_fwd_params(params, mtu);
> +	return bpf_fib_set_fwd_params(dev, params, flags, mtu, in_ifindex);
>  }
>  #endif
>  
> @@ -6357,6 +6377,7 @@ static int bpf_ipv6_fib_lookup(struct net *net, struct bpf_fib_lookup *params,
>  {
>  	struct in6_addr *src = (struct in6_addr *) params->ipv6_src;
>  	struct in6_addr *dst = (struct in6_addr *) params->ipv6_dst;
> +	u32 in_ifindex = params->ifindex;
>  	struct fib6_result res = {};
>  	struct neighbour *neigh;
>  	struct net_device *dev;
> @@ -6486,13 +6507,14 @@ static int bpf_ipv6_fib_lookup(struct net *net, struct bpf_fib_lookup *params,
>  	memcpy(params->smac, dev->dev_addr, ETH_ALEN);
>  
>  set_fwd_params:
> -	return bpf_fib_set_fwd_params(params, mtu);
> +	return bpf_fib_set_fwd_params(dev, params, flags, mtu, in_ifindex);
>  }
>  #endif
>  
>  #define BPF_FIB_LOOKUP_MASK (BPF_FIB_LOOKUP_DIRECT | BPF_FIB_LOOKUP_OUTPUT | \
>  			     BPF_FIB_LOOKUP_SKIP_NEIGH | BPF_FIB_LOOKUP_TBID | \
> -			     BPF_FIB_LOOKUP_SRC | BPF_FIB_LOOKUP_MARK)
> +			     BPF_FIB_LOOKUP_SRC | BPF_FIB_LOOKUP_MARK | \
> +			     BPF_FIB_LOOKUP_VLAN)
>  
>  BPF_CALL_4(bpf_xdp_fib_lookup, struct xdp_buff *, ctx,
>  	   struct bpf_fib_lookup *, params, int, plen, u32, flags)
> @@ -6541,6 +6563,9 @@ BPF_CALL_4(bpf_skb_fib_lookup, struct sk_buff *, skb,
>  	if (flags & ~BPF_FIB_LOOKUP_MASK)
>  		return -EINVAL;
>  
> +	if (flags & BPF_FIB_LOOKUP_VLAN)
> +		return -EINVAL;
> +
>  	if (params->tot_len)
>  		check_mtu = true;
>  
> diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h
> index 89b36de5fdbb..e00f0392e728 100644
> --- a/tools/include/uapi/linux/bpf.h
> +++ b/tools/include/uapi/linux/bpf.h
> @@ -3532,6 +3532,29 @@ union bpf_attr {
>   *			Use the mark present in *params*->mark for the fib lookup.
>   *			This option should not be used with BPF_FIB_LOOKUP_DIRECT,
>   *			as it only has meaning for full lookups.
> + *		**BPF_FIB_LOOKUP_VLAN**
> + *			If the fib lookup resolves to a VLAN device whose
> + *			parent is a real (non-VLAN) device, set
> + *			*params*->h_vlan_proto and *params*->h_vlan_TCI from
> + *			the VLAN device and replace *params*->ifindex with the
> + *			parent's ifindex. *params*->h_vlan_TCI carries the VID
> + *			only, with PCP and DEI bits zero; a consumer wanting to
> + *			set egress priority writes PCP itself. *params*->smac is
> + *			the VLAN device's own address, which can differ from the
> + *			parent's. Only the immediate parent is resolved; if it
> + *			is itself a VLAN device (QinQ) or in another namespace,
> + *			the egress cannot be reduced to a physical device plus
> + *			one tag and the lookup returns
> + *			**BPF_FIB_LKUP_RET_VLAN_FAILURE** with *params*->ifindex
> + *			left at the input. Re-issue without
> + *			**BPF_FIB_LOOKUP_VLAN** to obtain the VLAN device's own
> + *			ifindex. The swap and the vlan fields
> + *			are written only on success; other output fields keep
> + *			the helper's existing behaviour, so a frag-needed result
> + *			still reports the route mtu in *params*->mtu_result.
> + *			This flag is only valid for XDP programs; tc programs
> + *			receive -EINVAL since they can redirect to the VLAN
> + *			device directly.
>   *
>   *		*ctx* is either **struct xdp_md** for XDP programs or
>   *		**struct sk_buff** tc cls_act programs.
> @@ -7327,6 +7350,7 @@ enum {
>  	BPF_FIB_LOOKUP_TBID    = (1U << 3),
>  	BPF_FIB_LOOKUP_SRC     = (1U << 4),
>  	BPF_FIB_LOOKUP_MARK    = (1U << 5),
> +	BPF_FIB_LOOKUP_VLAN    = (1U << 6),
>  };
>  
>  enum {
> @@ -7340,6 +7364,7 @@ enum {
>  	BPF_FIB_LKUP_RET_NO_NEIGH,     /* no neighbor entry for nh */
>  	BPF_FIB_LKUP_RET_FRAG_NEEDED,  /* fragmentation required to fwd */
>  	BPF_FIB_LKUP_RET_NO_SRC_ADDR,  /* failed to derive IP src addr */
> +	BPF_FIB_LKUP_RET_VLAN_FAILURE, /* VLAN egress, parent unresolvable */
>  };
>  
>  struct bpf_fib_lookup {
> @@ -7393,7 +7418,11 @@ struct bpf_fib_lookup {
>  
>  	union {
>  		struct {
> -			/* output */
> +			/*
> +			 * output with BPF_FIB_LOOKUP_VLAN: set from the
> +			 * resolved egress VLAN device (see the flag); zeroed
> +			 * on other successful lookups.
> +			 */
>  			__be16	h_vlan_proto;
>  			__be16	h_vlan_TCI;
>  		};


^ permalink raw reply

* [PATCH net-next V5 6/6] net/mlx5: Apply devlink eswitch mode boot default on probe
From: Mark Bloch @ 2026-07-07 17:45 UTC (permalink / raw)
  To: Jiri Pirko, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman
  Cc: Saeed Mahameed, Leon Romanovsky, Tariq Toukan, Andrew Lunn,
	Jonathan Corbet, Shuah Khan, netdev, linux-rdma, linux-doc,
	Mark Bloch
In-Reply-To: <20260707174527.425134-1-mbloch@nvidia.com>

Apply devlink_eswitch_mode= boot defaults for mlx5 after the initial
probe finishes device initialization while holding the devlink instance
lock.

At this point the devlink instance is registered and mlx5 can perform an
eswitch mode change. Calling devl_apply_default_esw_mode() also clears
any pending default apply work queued by devl_register(), so the queued
work will not apply the same default again.

Keep this call in mlx5_init_one() rather than the lower-level
devl-locked init helper. That helper is also used by devlink reload, and
devlink core already applies the boot default after a successful
DRIVER_REINIT reload.

Signed-off-by: Mark Bloch <mbloch@nvidia.com>
---
 drivers/net/ethernet/mellanox/mlx5/core/main.c | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/drivers/net/ethernet/mellanox/mlx5/core/main.c b/drivers/net/ethernet/mellanox/mlx5/core/main.c
index 643b4aac2033..0712efea74cc 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/main.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/main.c
@@ -1392,6 +1392,17 @@ static void mlx5_unload(struct mlx5_core_dev *dev)
 	mlx5_free_bfreg(dev, &dev->priv.bfreg);
 }
 
+static void mlx5_devl_apply_default_esw_mode(struct mlx5_core_dev *dev)
+{
+	struct devlink *devlink = priv_to_devlink(dev);
+
+	if (!MLX5_ESWITCH_MANAGER(dev))
+		return;
+
+	devl_assert_locked(devlink);
+	devl_apply_default_esw_mode(devlink);
+}
+
 int mlx5_init_one_devl_locked(struct mlx5_core_dev *dev)
 {
 	bool light_probe = mlx5_dev_is_lightweight(dev);
@@ -1471,6 +1482,8 @@ int mlx5_init_one(struct mlx5_core_dev *dev)
 	err = mlx5_init_one_devl_locked(dev);
 	if (err)
 		devl_unregister(devlink);
+	else
+		mlx5_devl_apply_default_esw_mode(dev);
 unlock:
 	devl_unlock(devlink);
 	return err;
-- 
2.43.0


^ permalink raw reply related

* [PATCH net-next V5 4/6] devlink: Apply eswitch mode boot defaults
From: Mark Bloch @ 2026-07-07 17:45 UTC (permalink / raw)
  To: Jiri Pirko, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman
  Cc: Saeed Mahameed, Leon Romanovsky, Tariq Toukan, Andrew Lunn,
	Jonathan Corbet, Shuah Khan, netdev, linux-rdma, linux-doc,
	Mark Bloch
In-Reply-To: <20260707174527.425134-1-mbloch@nvidia.com>

Apply parsed devlink_eswitch_mode= defaults after devlink registration
and after successful reload.

devl_register() may still be called before the device is ready for an
eswitch mode change. Keep the registration path passive and let the
regular devl_unlock() path queue the async apply work once the instance
is registered and the default is still pending.

The queueing path runs while the devlink instance lock is held, so the
queued work gets its devlink reference before the caller drops the lock.
The worker then takes the devlink instance lock normally and applies the
default only if the instance is still registered and the default is still
pending.

For successful reloads that performed DRIVER_REINIT, devlink_reload()
already holds the devlink instance lock and the driver has completed
reload_up(). Clear pending work and apply the default directly from the
reload path instead of queueing work.

Preserve the user configured mode when it is set before devlink applies
the default.

Signed-off-by: Mark Bloch <mbloch@nvidia.com>
---
 net/devlink/core.c          |   6 ++
 net/devlink/default.c       | 112 +++++++++++++++++++++++++++++++++++-
 net/devlink/dev.c           |   6 ++
 net/devlink/devl_internal.h |   7 +++
 4 files changed, 129 insertions(+), 2 deletions(-)

diff --git a/net/devlink/core.c b/net/devlink/core.c
index fc14ee5d9dcf..9d9cc40626fc 100644
--- a/net/devlink/core.c
+++ b/net/devlink/core.c
@@ -317,6 +317,7 @@ EXPORT_SYMBOL_GPL(devl_trylock);
 
 void devl_unlock(struct devlink *devlink)
 {
+	devlink_default_esw_mode_queue_apply_work(devlink);
 	mutex_unlock(&devlink->lock);
 }
 EXPORT_SYMBOL_GPL(devl_unlock);
@@ -429,6 +430,7 @@ void devl_unregister(struct devlink *devlink)
 	ASSERT_DEVLINK_REGISTERED(devlink);
 	devl_assert_locked(devlink);
 
+	devlink_default_esw_mode_apply_pending_clear(devlink);
 	devlink_notify_unregister(devlink);
 	xa_clear_mark(&devlinks, devlink->index, DEVLINK_REGISTERED);
 	devlink_rel_put(devlink);
@@ -490,6 +492,7 @@ struct devlink *__devlink_alloc(const struct devlink_ops *ops, size_t priv_size,
 	INIT_LIST_HEAD(&devlink->trap_group_list);
 	INIT_LIST_HEAD(&devlink->trap_policer_list);
 	INIT_RCU_WORK(&devlink->rwork, devlink_release);
+	devlink_default_esw_mode_instance_init(devlink);
 	lockdep_register_key(&devlink->lock_key);
 	mutex_init(&devlink->lock);
 	lockdep_set_class(&devlink->lock, &devlink->lock_key);
@@ -537,6 +540,9 @@ void devlink_free(struct devlink *devlink)
 	devl_lock(devlink);
 	WARN_ON(devlink_rates_check(devlink, NULL, NULL));
 	devl_unlock(devlink);
+
+	devlink_default_esw_mode_instance_cleanup(devlink);
+
 	devlink_rel_put(devlink);
 
 	WARN_ON(!list_empty(&devlink->trap_policer_list));
diff --git a/net/devlink/default.c b/net/devlink/default.c
index 8434af83ea69..896146d1b8e7 100644
--- a/net/devlink/default.c
+++ b/net/devlink/default.c
@@ -10,8 +10,10 @@
 
 static char *devlink_default_esw_mode_param;
 static bool devlink_default_esw_mode_match_all;
+static bool devlink_default_esw_mode_enabled;
 static enum devlink_eswitch_mode devlink_default_esw_mode;
 static LIST_HEAD(devlink_default_esw_mode_nodes);
+static struct workqueue_struct *devlink_default_esw_mode_wq;
 
 struct devlink_default_esw_mode_node {
 	struct list_head list;
@@ -154,6 +156,7 @@ static void __init devlink_default_esw_mode_nodes_clear(void)
 	}
 
 	devlink_default_esw_mode_match_all = false;
+	devlink_default_esw_mode_enabled = false;
 }
 
 static int __init devlink_default_esw_mode_parse(char *str)
@@ -180,14 +183,108 @@ static int __init devlink_default_esw_mode_parse(char *str)
 		return err;
 
 	err = devlink_default_esw_mode_handles_parse(handles);
-	if (err)
+	if (err) {
 		devlink_default_esw_mode_nodes_clear();
-	else
+	} else {
 		devlink_default_esw_mode = esw_mode;
+		devlink_default_esw_mode_enabled = true;
+	}
 
 	return err;
 }
 
+static bool devlink_default_esw_mode_match(struct devlink *devlink)
+{
+	const char *bus_name = devlink_bus_name(devlink);
+	const char *dev_name = devlink_dev_name(devlink);
+	struct devlink_default_esw_mode_node *node;
+
+	if (devlink_default_esw_mode_match_all)
+		return true;
+
+	node = devlink_default_esw_mode_node_find(bus_name, dev_name);
+	return !!node;
+}
+
+void devlink_default_esw_mode_apply_locked(struct devlink *devlink)
+{
+	const struct devlink_ops *ops = devlink->ops;
+	int err;
+
+	devl_assert_locked(devlink);
+
+	if (!devlink_default_esw_mode_match(devlink))
+		return;
+
+	if (!ops->eswitch_mode_set) {
+		if (!devlink_default_esw_mode_match_all)
+			devl_warn(devlink,
+				  "devlink_eswitch_mode= selected this device but eswitch mode setting is not supported\n");
+		return;
+	}
+
+	err = devlink_eswitch_mode_set(devlink, devlink_default_esw_mode, NULL);
+	if (err)
+		devl_warn(devlink,
+			  "Couldn't apply default eswitch mode, err %d\n",
+			  err);
+}
+
+void devlink_default_esw_mode_queue_apply_work(struct devlink *devlink)
+{
+	devl_assert_locked(devlink);
+
+	if (!devlink_default_esw_mode_enabled || !devlink_default_esw_mode_wq)
+		return;
+	if (!devlink->default_esw_mode_apply_pending ||
+	    !__devl_is_registered(devlink))
+		return;
+	if (!devlink_try_get(devlink))
+		return;
+	if (!queue_work(devlink_default_esw_mode_wq,
+			&devlink->default_esw_mode_apply_work))
+		devlink_put(devlink);
+}
+
+static void devlink_default_esw_mode_apply_work(struct work_struct *work)
+{
+	struct devlink *devlink;
+
+	devlink = container_of(work, struct devlink,
+			       default_esw_mode_apply_work);
+
+	devl_lock(devlink);
+
+	if (devl_is_registered(devlink) &&
+	    devlink->default_esw_mode_apply_pending) {
+		devlink_default_esw_mode_apply_locked(devlink);
+		devlink->default_esw_mode_apply_pending = false;
+	}
+
+	devl_unlock(devlink);
+	devlink_put(devlink);
+}
+
+void devlink_default_esw_mode_instance_init(struct devlink *devlink)
+{
+	INIT_WORK(&devlink->default_esw_mode_apply_work,
+		  devlink_default_esw_mode_apply_work);
+	devlink->default_esw_mode_apply_pending = true;
+}
+
+void devlink_default_esw_mode_apply_pending_clear(struct devlink *devlink)
+{
+	devl_assert_locked(devlink);
+
+	devlink->default_esw_mode_apply_pending = false;
+}
+
+void devlink_default_esw_mode_instance_cleanup(struct devlink *devlink)
+{
+	if (cancel_work_sync(&devlink->default_esw_mode_apply_work))
+		devlink_put(devlink);
+}
+
 static int __init devlink_default_esw_mode_setup(char *str)
 {
 	devlink_default_esw_mode_param = str;
@@ -228,10 +325,21 @@ int __init devlink_default_esw_mode_init(void)
 		return err;
 	}
 
+	devlink_default_esw_mode_wq = alloc_workqueue("devlink_default_esw_mode",
+						      WQ_UNBOUND | WQ_MEM_RECLAIM,
+						      0);
+	if (!devlink_default_esw_mode_wq) {
+		devlink_default_esw_mode_param = NULL;
+		devlink_default_esw_mode_nodes_clear();
+		pr_warn("devlink: devlink_eswitch_mode parameter ignored, failed to allocate workqueue\n");
+	}
+
 	return 0;
 }
 
 void __init devlink_default_esw_mode_cleanup(void)
 {
+	if (devlink_default_esw_mode_wq)
+		destroy_workqueue(devlink_default_esw_mode_wq);
 	devlink_default_esw_mode_nodes_clear();
 }
diff --git a/net/devlink/dev.c b/net/devlink/dev.c
index 119ef105d0a7..611bb6bfd492 100644
--- a/net/devlink/dev.c
+++ b/net/devlink/dev.c
@@ -478,6 +478,11 @@ int devlink_reload(struct devlink *devlink, struct net *dest_net,
 		return err;
 
 	WARN_ON(!(*actions_performed & BIT(action)));
+	if (*actions_performed & BIT(DEVLINK_RELOAD_ACTION_DRIVER_REINIT)) {
+		devlink_default_esw_mode_apply_pending_clear(devlink);
+		devlink_default_esw_mode_apply_locked(devlink);
+	}
+
 	/* Catch driver on updating the remote action within devlink reload */
 	WARN_ON(memcmp(remote_reload_stats, devlink->stats.remote_reload_stats,
 		       sizeof(remote_reload_stats)));
@@ -731,6 +736,7 @@ int devlink_nl_eswitch_set_doit(struct sk_buff *skb, struct genl_info *info)
 	u16 mode;
 
 	if (info->attrs[DEVLINK_ATTR_ESWITCH_MODE]) {
+		devlink_default_esw_mode_apply_pending_clear(devlink);
 		mode = nla_get_u16(info->attrs[DEVLINK_ATTR_ESWITCH_MODE]);
 		err = devlink_eswitch_mode_set(devlink, mode, info->extack);
 		if (err)
diff --git a/net/devlink/devl_internal.h b/net/devlink/devl_internal.h
index fe9ad58515d4..c2bee5aabd49 100644
--- a/net/devlink/devl_internal.h
+++ b/net/devlink/devl_internal.h
@@ -58,8 +58,10 @@ struct devlink {
 	struct mutex lock;
 	struct lock_class_key lock_key;
 	u8 reload_failed:1;
+	u8 default_esw_mode_apply_pending:1;
 	refcount_t refcount;
 	struct rcu_work rwork;
+	struct work_struct default_esw_mode_apply_work;
 	struct devlink_rel *rel;
 	struct xarray nested_rels;
 	char priv[] __aligned(NETDEV_ALIGN);
@@ -73,6 +75,11 @@ struct devlink *__devlink_alloc(const struct devlink_ops *ops, size_t priv_size,
 				const struct device_driver *dev_driver);
 int devlink_default_esw_mode_init(void);
 void devlink_default_esw_mode_cleanup(void);
+void devlink_default_esw_mode_instance_init(struct devlink *devlink);
+void devlink_default_esw_mode_apply_locked(struct devlink *devlink);
+void devlink_default_esw_mode_queue_apply_work(struct devlink *devlink);
+void devlink_default_esw_mode_apply_pending_clear(struct devlink *devlink);
+void devlink_default_esw_mode_instance_cleanup(struct devlink *devlink);
 
 #define devl_warn(devlink, format, args...)				\
 	do {								\
-- 
2.43.0


^ permalink raw reply related

* [PATCH net-next V5 5/6] devlink: Add API to apply eswitch mode boot default
From: Mark Bloch @ 2026-07-07 17:45 UTC (permalink / raw)
  To: Jiri Pirko, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman
  Cc: Saeed Mahameed, Leon Romanovsky, Tariq Toukan, Andrew Lunn,
	Jonathan Corbet, Shuah Khan, netdev, linux-rdma, linux-doc,
	Mark Bloch
In-Reply-To: <20260707174527.425134-1-mbloch@nvidia.com>

Add devl_apply_default_esw_mode() for drivers that can apply the
devlink_eswitch_mode= boot default once their device is ready instead of
waiting for the asynchronous registration work.

Signed-off-by: Mark Bloch <mbloch@nvidia.com>
---
 include/net/devlink.h |  1 +
 net/devlink/default.c | 19 +++++++++++++++++++
 2 files changed, 20 insertions(+)

diff --git a/include/net/devlink.h b/include/net/devlink.h
index ffe1ad5fb70b..90da03c0112c 100644
--- a/include/net/devlink.h
+++ b/include/net/devlink.h
@@ -1661,6 +1661,7 @@ static inline struct devlink *devlink_alloc(const struct devlink_ops *ops,
 
 int devl_register(struct devlink *devlink);
 void devl_unregister(struct devlink *devlink);
+void devl_apply_default_esw_mode(struct devlink *devlink);
 void devlink_register(struct devlink *devlink);
 void devlink_unregister(struct devlink *devlink);
 void devlink_free(struct devlink *devlink);
diff --git a/net/devlink/default.c b/net/devlink/default.c
index 896146d1b8e7..5a37d76731e1 100644
--- a/net/devlink/default.c
+++ b/net/devlink/default.c
@@ -2,6 +2,7 @@
 /* Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. */
 
 #include <linux/init.h>
+#include <linux/export.h>
 #include <linux/list.h>
 #include <linux/slab.h>
 #include <linux/string.h>
@@ -279,6 +280,24 @@ void devlink_default_esw_mode_apply_pending_clear(struct devlink *devlink)
 	devlink->default_esw_mode_apply_pending = false;
 }
 
+/**
+ * devl_apply_default_esw_mode - Apply devlink eswitch mode boot default
+ * @devlink: devlink
+ *
+ * Apply the devlink eswitch mode selected by the devlink_eswitch_mode=
+ * kernel command line parameter, if any matches @devlink.
+ *
+ * The caller must hold the devlink instance lock.
+ */
+void devl_apply_default_esw_mode(struct devlink *devlink)
+{
+	devl_assert_locked(devlink);
+
+	devlink->default_esw_mode_apply_pending = false;
+	devlink_default_esw_mode_apply_locked(devlink);
+}
+EXPORT_SYMBOL_GPL(devl_apply_default_esw_mode);
+
 void devlink_default_esw_mode_instance_cleanup(struct devlink *devlink)
 {
 	if (cancel_work_sync(&devlink->default_esw_mode_apply_work))
-- 
2.43.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