Netdev List
 help / color / mirror / Atom feed
* Re: [PATCH] net: pcs: xpcs-plat: fix runtime PM initialization
From: Coia Prant @ 2026-07-21  1:53 UTC (permalink / raw)
  To: Jakub Kicinski
  Cc: netdev, Andrew Lunn, Heiner Kallweit, Russell King,
	David S . Miller, Eric Dumazet, Paolo Abeni, Serge Semin,
	linux-kernel, stable
In-Reply-To: <20260720172433.77b83dc7@kernel.org>

On July 21, 2026 8:24:33 AM GMT+08:00, Jakub Kicinski <kuba@kernel.org> wrote:
>On Sun,  5 Jul 2026 05:48:08 +0800 Coia Prant wrote:
>> The driver calls `pm_runtime_set_active()` before runtime PM is enabled,
>> and before the clock is prepared and enabled.
>> 
>> This causes the clock to be unprepared/disabled later in the suspend
>> callback even though it was never prepared/enabled, resulting in warnings:
>> 
>> clk_csr already disabled
>> clk_csr already unprepared
>> 
>> Fix this by setting the initial runtime PM status to SUSPENDED instead
>> of ACTIVE.
>> 
>> The clock will be properly enabled when the device is first resumed
>> via runtime PM (e.g., during MDIO access).
>
>Seems a bit odd that this hasn't been discovered until now.
>Could you add more details about your platform and maybe
>a hypothesis why we haven't noticed?

Hi,

I came across what looks like a runtime PM initialization issue while
using pcs-xpcs-plat.c as a reference for the Rockchip XPCS glue driver
(drivers/net/pcs/pcs-xpcs-rk.c).

The current code in pcs-xpcs-plat.c does:

    pm_runtime_set_active(dev);
    ret = devm_pm_runtime_enable(dev);

This sets the initial PM state to ACTIVE before runtime PM is fully
enabled, and before the clock is prepared and enabled.

If the device is later suspended (e.g., during unbind), the suspend
callback may try to disable a clock that was never enabled, leading to:

    clk_csr already disabled
    clk_csr already unprepared

On Rockchip platforms, the CSR clock (PCLK_XPCS) is required for register
access, and this pattern seems problematic when a clock is actually
provided.

I have a few questions:

1. Is there a reason this hasn't been noticed before?
   As far as I can tell, there is currently no mainline device tree
   user that enables this driver on a platform with a real clock
   dependency. Out-of-tree users might be using it without any clock
   at all, or ACPI users might behave differently.

2. Should we select PM in Kconfig and drop __maybe_unused from the
   PM callbacks? Since this driver relies on runtime PM for clock
   management, it seems odd to allow !PM builds.

3. Should we add a .remove callback to force suspend the device on
   unbind? Otherwise the clock might remain enabled if the driver is
   removed while active.

I'm happy to send a follow-up patch addressing these points if you
agree with the direction. Let me know what you think.

Thanks,
Coia

^ permalink raw reply

* [PATCH v4 net-next] sctp: auth: break when skb_clone fails for auth_chunk
From: luoqing @ 2026-07-21  1:55 UTC (permalink / raw)
  To: marcelo.leitner, lucien.xin, davem, edumazet, kuba, pabeni
  Cc: horms, linux-sctp, netdev, linux-kernel

From: Qing Luo <luoqing@kylinos.cn>

When processing AUTH + COOKIE-ECHO packets, if skb_clone() fails
due to memory pressure, chunk->auth_chunk is NULL. The original
code still sets chunk->auth = 1 and continues, leaving the
COOKIE-ECHO to be processed without a valid auth_chunk for
deferred verification.

The intent of not setting auth was to drop the chunk earlier,
but in sctp_endpoint_bh_rcv() asoc is NULL for new connections,
so sctp_auth_recv_cid() returns 0 and the early check is
ineffective.

Fix by breaking out of the receive loop when skb_clone() fails,
dropping the entire packet since the AUTH data needed for
COOKIE-ECHO verification cannot be preserved.

Fixes: bbd0d59809f9 ("[SCTP]: Implement the receive and verification of AUTH chunk")
Signed-off-by: Qing Luo <luoqing@kylinos.cn>
---
 net/sctp/associola.c   | 2 ++
 net/sctp/endpointola.c | 2 ++
 2 files changed, 4 insertions(+)

diff --git a/net/sctp/associola.c b/net/sctp/associola.c
index 62d3cc155809..7741f982e368 100644
--- a/net/sctp/associola.c
+++ b/net/sctp/associola.c
@@ -999,6 +999,8 @@ static void sctp_assoc_bh_rcv(struct work_struct *work)
 			if (next_hdr->type == SCTP_CID_COOKIE_ECHO) {
 				chunk->auth_chunk = skb_clone(chunk->skb,
 							      GFP_ATOMIC);
+				if (!chunk->auth_chunk)
+					break;
 				chunk->auth = 1;
 				continue;
 			}
diff --git a/net/sctp/endpointola.c b/net/sctp/endpointola.c
index dfb1719275db..9675370a46da 100644
--- a/net/sctp/endpointola.c
+++ b/net/sctp/endpointola.c
@@ -368,6 +368,8 @@ static void sctp_endpoint_bh_rcv(struct work_struct *work)
 			if (next_hdr->type == SCTP_CID_COOKIE_ECHO) {
 				chunk->auth_chunk = skb_clone(chunk->skb,
 								GFP_ATOMIC);
+				if (!chunk->auth_chunk)
+					break;
 				chunk->auth = 1;
 				continue;
 			}
-- 
2.25.1


^ permalink raw reply related

* [PATCH v4 net] sctp: auth: verify auth requirement when auth_chunk is NULL
From: luoqing @ 2026-07-21  1:55 UTC (permalink / raw)
  To: marcelo.leitner, lucien.xin, davem, edumazet, kuba, pabeni
  Cc: horms, linux-sctp, netdev, linux-kernel
In-Reply-To: <20260721015532.120157-1-l1138897701@163.com>

From: Qing Luo <luoqing@kylinos.cn>

sctp_auth_chunk_verify() returns true unconditionally when
chunk->auth_chunk is NULL, silently skipping authentication.
This is incorrect when:

1. skb_clone() failed in the BH receive path, leaving auth_chunk
   NULL. In sctp_endpoint_bh_rcv() asoc is NULL for new
   connections, so the early sctp_auth_recv_cid() check cannot
   catch this.

2. No AUTH chunk precedes COOKIE-ECHO, so skb_clone() is never
   called and auth_chunk remains NULL.

Fix by checking sctp_auth_recv_cid() when auth_chunk is NULL:
if authentication is required, return false to drop the chunk;
otherwise continue normally.

Fixes: bbd0d59809f9 ("[SCTP]: Implement the receive and verification of AUTH chunk")
Signed-off-by: Qing Luo <luoqing@kylinos.cn>
---
 net/sctp/sm_statefuns.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/net/sctp/sm_statefuns.c b/net/sctp/sm_statefuns.c
index d23d935e128e..89ed618b1de3 100644
--- a/net/sctp/sm_statefuns.c
+++ b/net/sctp/sm_statefuns.c
@@ -642,7 +642,7 @@ static bool sctp_auth_chunk_verify(struct net *net, struct sctp_chunk *chunk,
 	struct sctp_chunk auth;
 
 	if (!chunk->auth_chunk)
-		return true;
+		return !sctp_auth_recv_cid(chunk->chunk_hdr->type, asoc);
 
 	/* SCTP-AUTH:  auth_chunk pointer is only set when the cookie-echo
 	 * is supposed to be authenticated and we have to do delayed
-- 
2.25.1


^ permalink raw reply related

* [PATCH net] net/packet: defer vmalloc TX_RING free until skbs finish
From: Kyle Zeng @ 2026-07-21  1:58 UTC (permalink / raw)
  To: netdev
  Cc: Jakub Kicinski, Eric Dumazet, David S . Miller, Willem de Bruijn,
	Kyle Zeng, stable

AF_PACKET TX_RING skbs keep a raw pointer to their ring frame. The skb
page references preserve page-backed ring blocks after pg_vec is freed,
but they do not preserve a vmalloc mapping.

tpacket_destruct_skb() currently drops the pending reference before
writing the timestamp and TP_STATUS_AVAILABLE to the frame. Move the
decrement after those stores. The smp_wmb() in __packet_set_status()
orders the frame stores before the decrement.

On socket close, scan every pg_vec entry because allocation can produce
a mixture of page-backed and vmalloc-backed blocks. If any block is
vmalloc-backed and TX skbs remain pending, defer the whole vector to
system_long_wq.

After pg_vec is detached, a late destructor can skip the pending
decrement. Use socket write-memory accounting as the deferred lifetime
gate instead: an skb remains charged through its final sock_wfree(),
after all ring-frame accesses. The delayed work retains a socket
reference and reschedules itself until no TX skbs remain. Fall back to
a synchronous wait if the work allocation fails.

Move pending_refcnt release to packet_sock_destruct() so late skb
destructors and deferred cleanup can safely use it after
packet_release(). Page-backed teardown remains synchronous, and no lock
is added to the TX completion hot path.

Fixes: b013840810c2 ("packet: use percpu mmap tx frame pending refcount")
Cc: stable@vger.kernel.org
Suggested-by: Willem de Bruijn <willemdebruijn.kernel@gmail.com>
Assisted-by: Codex:gpt-5.6
Signed-off-by: Kyle Zeng <kylebot@openai.com>
---
 net/packet/af_packet.c | 90 ++++++++++++++++++++++++++++++++++++++----
 1 file changed, 82 insertions(+), 8 deletions(-)

diff --git a/net/packet/af_packet.c b/net/packet/af_packet.c
index 8e6f3a734ba0..1a4c548c59ac 100644
--- a/net/packet/af_packet.c
+++ b/net/packet/af_packet.c
@@ -88,6 +88,7 @@
 #include <linux/errqueue.h>
 #include <linux/net_tstamp.h>
 #include <linux/percpu.h>
+#include <linux/workqueue.h>
 #ifdef CONFIG_INET
 #include <net/inet_common.h>
 #endif
@@ -1329,6 +1330,8 @@ static void packet_sock_destruct(struct sock *sk)
 	WARN_ON(atomic_read(&sk->sk_rmem_alloc));
 	WARN_ON(refcount_read(&sk->sk_wmem_alloc));
 
+	packet_free_pending(pkt_sk(sk));
+
 	if (!sock_flag(sk, SOCK_DEAD)) {
 		pr_err("Attempt to release alive packet socket: %p\n", sk);
 		return;
@@ -2516,14 +2519,12 @@ static void tpacket_destruct_skb(struct sk_buff *skb)
 		__u32 ts;
 
 		ph = skb_zcopy_get_nouarg(skb);
-		packet_dec_pending(&po->tx_ring);
 
 		ts = __packet_set_timestamp(po, ph, skb);
 		__packet_set_status(po, ph, TP_STATUS_AVAILABLE | ts);
-
+		packet_dec_pending(&po->tx_ring);
 		complete(&po->skb_completion);
 	}
-
 	sock_wfree(skb);
 }
 
@@ -3182,7 +3183,6 @@ static int packet_release(struct socket *sock)
 	/* Purge queues */
 
 	skb_queue_purge(&sk->sk_receive_queue);
-	packet_free_pending(po);
 
 	sock_put(sk);
 	return 0;
@@ -4345,8 +4345,16 @@ static const struct vm_operations_struct packet_mmap_ops = {
 	.close	=	packet_mm_close,
 };
 
-static void free_pg_vec(struct pgv *pg_vec, unsigned int order,
-			unsigned int len)
+struct packet_pg_vec_free {
+	struct delayed_work work;
+	struct sock *sk;
+	struct pgv *pg_vec;
+	unsigned int order;
+	unsigned int len;
+};
+
+static void __free_pg_vec(struct pgv *pg_vec, unsigned int order,
+			  unsigned int len)
 {
 	int i;
 
@@ -4363,6 +4371,69 @@ static void free_pg_vec(struct pgv *pg_vec, unsigned int order,
 	kfree(pg_vec);
 }
 
+static void packet_wait_for_tx_skbs(struct packet_sock *po)
+{
+	while (sk_wmem_alloc_get(&po->sk))
+		wait_for_completion_timeout(&po->skb_completion, 1);
+}
+
+static void packet_free_pg_vec_work(struct work_struct *work)
+{
+	struct packet_pg_vec_free *deferred;
+	struct sock *sk;
+
+	deferred = container_of(to_delayed_work(work),
+				struct packet_pg_vec_free, work);
+	sk = deferred->sk;
+	if (sk_wmem_alloc_get(sk)) {
+		queue_delayed_work(system_long_wq, &deferred->work, 1);
+		return;
+	}
+
+	__free_pg_vec(deferred->pg_vec, deferred->order, deferred->len);
+	kfree(deferred);
+	sock_put(sk);
+}
+
+static bool pg_vec_has_vmalloc(struct pgv *pg_vec, unsigned int len)
+{
+	int i;
+
+	for (i = 0; i < len; i++)
+		if (pg_vec[i].buffer && is_vmalloc_addr(pg_vec[i].buffer))
+			return true;
+
+	return false;
+}
+
+static void free_pg_vec(struct packet_sock *po, struct pgv *pg_vec,
+			unsigned int order, unsigned int len)
+{
+	struct packet_pg_vec_free *deferred;
+
+	if (!pg_vec_has_vmalloc(pg_vec, len) ||
+	    !packet_read_pending(&po->tx_ring)) {
+		__free_pg_vec(pg_vec, order, len);
+		return;
+	}
+
+	deferred = kmalloc(sizeof(*deferred), GFP_KERNEL);
+	if (!deferred) {
+		packet_wait_for_tx_skbs(po);
+		__free_pg_vec(pg_vec, order, len);
+		return;
+	}
+
+	INIT_DELAYED_WORK(&deferred->work, packet_free_pg_vec_work);
+	deferred->sk = &po->sk;
+	deferred->pg_vec = pg_vec;
+	deferred->order = order;
+	deferred->len = len;
+
+	sock_hold(deferred->sk);
+	queue_delayed_work(system_long_wq, &deferred->work, 0);
+}
+
 static char *alloc_one_pg_vec_page(unsigned long order)
 {
 	char *buffer;
@@ -4408,7 +4479,7 @@ static struct pgv *alloc_pg_vec(struct tpacket_req *req, int order)
 	return pg_vec;
 
 out_free_pgvec:
-	free_pg_vec(pg_vec, order, block_nr);
+	__free_pg_vec(pg_vec, order, block_nr);
 	pg_vec = NULL;
 	goto out;
 }
@@ -4574,7 +4645,10 @@ static int packet_set_ring(struct sock *sk, union tpacket_req_u *req_u,
 out_free_pg_vec:
 	if (pg_vec) {
 		bitmap_free(rx_owner_map);
-		free_pg_vec(pg_vec, order, req->tp_block_nr);
+		if (tx_ring && closing)
+			free_pg_vec(po, pg_vec, order, req->tp_block_nr);
+		else
+			__free_pg_vec(pg_vec, order, req->tp_block_nr);
 	}
 out:
 	return err;
-- 
2.54.0


^ permalink raw reply related

* [PATCH v2] ice: parser: use kcalloc for table allocation
From: Weimin Xiong @ 2026-07-21  1:57 UTC (permalink / raw)
  To: intel-wired-lan
  Cc: aleksander.lobakin, anthony.l.nguyen, przemyslaw.kitszel, netdev,
	linux-kernel, andrew+netdev, davem, edumazet, kuba, pabeni
In-Reply-To: <28460003-47ee-4706-ad95-7fba87d36509@intel.com>

From 376dfdfc788a853c79a87c4bfdd94dc1ea678c43 Mon Sep 17 00:00:00 2001
From: Weimin Xiong <xiongwm2026@163.com>
Date: Tue, 21 Jul 2026 09:46:46 +0800
Subject: [PATCH v2] ice: parser: use kcalloc for table allocation
To: intel-wired-lan@lists.osuosl.org
Cc: Alexander Lobakin <aleksander.lobakin@intel.com>,
	Tony Nguyen <anthony.l.nguyen@intel.com>,
	Przemek Kitszel <przemyslaw.kitszel@intel.com>,
	netdev@vger.kernel.org,
	linux-kernel@vger.kernel.org,
	Andrew Lunn <andrew+netdev@lunn.ch>,
	"David S. Miller" <davem@davemloft.net>,
	"Eric Dumazet" <edumazet@google.com>,
	Jakub Kicinski <kuba@kernel.org>,
	Paolo Abeni <pabeni@redhat.com>
In-Reply-To: <28460003-47ee-4706-ad95-7fba87d36509@intel.com>
References: <20260716025100.118145-1-xiongwm2026@163.com>

Use kcalloc() when calculating the parser table allocation size so an
overflow in the firmware-provided item dimensions is detected before
allocation.

v2: Use kcalloc() instead of kzalloc(array_size()) as suggested by
    Alexander Lobakin <aleksander.lobakin@intel.com>

---

v1->v2:
  - Remove redundant include of <linux/overflow.h>
  - Use kcalloc() instead of kzalloc(array_size()) as suggested by
    Alexander Lobakin <aleksander.lobakin@intel.com>

Signed-off-by: Weimin Xiong <xiongwm2026@163.com>
---
 drivers/net/ethernet/intel/ice/ice_parser.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/net/ethernet/intel/ice/ice_parser.c b/drivers/net/ethernet/intel/ice/ice_parser.c
index f8e69630f..f5ae6c071 100644
--- a/drivers/net/ethernet/intel/ice/ice_parser.c
+++ b/drivers/net/ethernet/intel/ice/ice_parser.c
@@ -102,7 +102,7 @@ ice_parser_create_table(struct ice_hw *hw, u32 sect_type,
 	if (!seg)
 		return ERR_PTR(-EINVAL);
 
-	table = kzalloc(item_size * length, GFP_KERNEL);
+	table = kcalloc(length, item_size, GFP_KERNEL);
 	if (!table)
 		return ERR_PTR(-ENOMEM);
 

base-commit: 95e24e90b55ce6d9d266ed6f20514f37c931c751
-- 
2.43.0


^ permalink raw reply related

* RE: [PATCH RESEND v4 net-next 13/14] net: enetc: use alloc_etherdev_mqs() to create netdev for VF driver
From: Wei Fang (OSS) @ 2026-07-21  2:01 UTC (permalink / raw)
  To: Joe Damato, Wei Fang (OSS)
  Cc: Claudiu Manoil, Vladimir Oltean, Clark Wang,
	andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com,
	kuba@kernel.org, pabeni@redhat.com, linux@armlinux.org.uk,
	Wei Fang, chleroy@kernel.org, maxime.chevallier@bootlin.com,
	imx@lists.linux.dev, netdev@vger.kernel.org,
	linux-kernel@vger.kernel.org, linuxppc-dev@lists.ozlabs.org,
	linux-arm-kernel@lists.infradead.org
In-Reply-To: <al5cgs6CqVKsy/gh@devvm20253.cco0.facebook.com>

> On Mon, Jul 20, 2026 at 09:43:15AM +0800, wei.fang@oss.nxp.com wrote:
> > From: Wei Fang <wei.fang@nxp.com>
> >
> > The VF driver uses alloc_etherdev_mq() with ENETC_MAX_NUM_TXQS as the
> > queue count, which forces the TX and RX queue counts to be equal and
> > uses a compile-time constant rather than the actual hardware capability.
> >
> > After enetc_get_si_caps() is called, si->num_tx_rings and
> > si->num_rx_rings reflect the actual number of rings assigned to the VF
> > by the PF. For the ENETC VF on LS1028A and the upcoming i.MX95/94, their
> > SoCs have no more than 6 CPUs, and the number of TX/RX rings allocated
> > to the VF is less than 8.
> >
> > Therefore, switch to alloc_etherdev_mqs() so that the TX and RX queue
> > counts are set independently, each capped at ENETC_MAX_NUM_TXQS, based
> > on the actual number of rings assigned to the VF by the PF.
> >
> > Note that if future SoCs have more than 6 CPUs and more than 6 RX rings
> > allocated to VFs, the size of the int_vector array in struct
> > enetc_ndev_priv will need to be modified. Similarly, if more than 8 TX
> > rings are allocated to each int_vector, ENETC_MAX_NUM_TXQS will also
> > need to be modified.
> >
> > Signed-off-by: Wei Fang <wei.fang@nxp.com>
> > ---
> >  drivers/net/ethernet/freescale/enetc/enetc_vf.c | 9 ++++++++-
> >  1 file changed, 8 insertions(+), 1 deletion(-)
> >
> > diff --git a/drivers/net/ethernet/freescale/enetc/enetc_vf.c
> b/drivers/net/ethernet/freescale/enetc/enetc_vf.c
> > index 9cdb0a4d6baf..7dcb4a0246f5 100644
> > --- a/drivers/net/ethernet/freescale/enetc/enetc_vf.c
> > +++ b/drivers/net/ethernet/freescale/enetc/enetc_vf.c
> > @@ -317,7 +317,14 @@ static int enetc_vf_probe(struct pci_dev *pdev,
> >
> >       enetc_get_si_caps(si);
> >
> > -     ndev = alloc_etherdev_mq(sizeof(*priv), ENETC_MAX_NUM_TXQS);
> > +     /* Currently, the supported SoCs have a max of 6 CPUs and the VFs
> > +      * have less than 6 RX/TX rings. So no issues for these supported
> > +      * SoCs, but for future SoCs which have more CPUs or more TX/RX
> > +      * rings, all the related logic needs to be improved.
> > +      */
> > +     ndev = alloc_etherdev_mqs(sizeof(*priv),
> > +                               min(si->num_tx_rings,
> ENETC_MAX_NUM_TXQS),
> > +                               min(si->num_rx_rings,
> ENETC_MAX_NUM_TXQS));
> 
> Code looks right, but looks almost like a typo. I guess it would read nicer if
> ENETC_MAX_NUM_RXQS existed?

Yes, ENETC_MAX_NUM_RXQS is clearer. However, ENETC_MAX_NUM_RXQS
does not exist; in fact, their values ​​are equal, and we plan to remove
ENETC_MAX_NUM_TXQS later, so a new macro ENETC_MAX_NUM_RXQS
was not added in this patch.

> 
> That said:
> 
> Reviewed-by: Joe Damato <joe@dama.to>

^ permalink raw reply

* Re: [PATCH net 1/1] openvswitch: Fix CT limit teardown use-after-free
From: Yuan Tan @ 2026-07-21  2:02 UTC (permalink / raw)
  To: Aaron Conole, Andrew Lunn
  Cc: Ren Wei, xuyuqiabc, netdev, dev, echaudro, i.maximets, davem,
	edumazet, pabeni, horms, pshelar, yihung.wei, tonanli66
In-Reply-To: <f7tmrvla288.fsf@redhat.com>

On Mon, Jul 20, 2026 at 12:30 PM Aaron Conole <aconole@redhat.com> wrote:
>
> Andrew Lunn <andrew@lunn.ch> writes:
>
> > On Sun, Jul 19, 2026 at 11:54:31PM -0700, Yuan Tan wrote:
> > >
> > > On 7/19/26 19:52, Andrew Lunn wrote:
> > > > On Mon, Jul 20, 2026 at 10:14:16AM +0800, Ren Wei wrote:
> > > >> From: Yuqi Xu <xuyuqiabc@gmail.com>
> > > >>
> > > >> Packet processing uses CT limit state under RCU, while netns teardown
> > > >> frees that state under ovs_mutex. The CT limit pointer was neither removed
> > > >> from readers nor protected by a grace period, allowing packet processing to
> > > >> dereference the freed state.
> > > >>
> > > >> Replace the pointer before freeing the CT limit state. Wait for in-flight
> > > >> RCU readers before freeing its contents. Serialize CT limit netlink
> > > >> operations with teardown for the full lifetime of their state accesses.
> > > >>
> > > >> Fixes: 11efd5cb04a1 ("openvswitch: Support conntrack zone limit")
> > > >> Cc: stable@vger.kernel.org
> > > >> Reported-by: Vega <vega@nebusec.ai>
> > > > Is Vega a person?
> > >
> > > Hi Andrew,
> > >
> > > Thank you very much for your review!
> > > For context, we had previously understood that using the tool name in
> > > the Reported-by tag was acceptable, based on examples such as
> > > Reported-by: AutonomousCodeSecurity@microsoft.com and Reported-by:
> > > Anthropic.
> > >
> > > https://lore.kernel.org/all/20260630171016.11c02dec@kernel.org/
> >
> > https://docs.kernel.org/process/submitting-patches.html
> >
> >   The Reported-by tag gives credit to people who find bugs and report
> >   them and it hopefully inspires them to help us again in the
> >   future. The tag is intended for bugs; please do not use it to credit
> >   feature requests. The tag should be followed by a Closes: tag
> >   pointing to the report, unless the report is not available on the
> >   web.
> >
> > If you believe this is out of date, please submit a patch with new
> > text to this document.
>
> It is common practice to accept syzbot reports as well, which look like:
>
>     Reported-by: syzbot+36256deb69a588e9290e@syzkaller.appspotmail.com
>     Closes: https://syzkaller.appspot.com/bug?extid=36256deb69a588e9290e
>
> (see commit 539dfcf69105d8d3d4d677b71de6e5ede2e6dfa0 for example).
>
> > I find it valuable being a person. It indicate somebody is bothered by
> > the problem you are fixing. We see a lot theoretical bug fixes, which
> > in practice nobody ever hit. I would prefer to spend my time reviewing
> > real issues, not theoretical issues, and the Reported-by: is a quick
> > indicator of this.
>
> +1
>
> In the case of syzbot reports, they are real actionable reports that we
> can look at and address (and I agree with your sentiment of wanting to
> focus on real issues).  They include the 'Closes:' tag as well, and a
> reviewer can just visit the link and see the splat.  If there is a
> report link that this Vega tool pushes, maybe that would be acceptable
> since it's the way syzbot works as well.  It also makes sense to update
> the documentation to reflect how it is used currently.
>
> As for the v2, it would help my review to include some kind of
> reproducer description - I usually try to experience OVS splats for
> myself.
>

Hi Andrew and Aaron,

Thank you very much for your feedback!

In fact, all of the bugs for which we have submitted patches have been
reproduced with working PoCs. We also include detailed information
about the bug, the PoC, and the QEMU crash log in the cover letter.
Previously, however, we did not send the cover letters to the public
mailing list due to security concerns.

For example, this particular bug is a use-after-free write that can be
triggered from a user namespace. Based on our analysis, we believe it
is likely exploitable for local privilege escalation.

Going forward, we will send all cover letters to the public mailing
list. We apologize for any inconvenience caused by our previous
approach.

In addition, we plan to launch a website (in approximately one week)
containing the bug detail, PoCs and crash logs for all the bugs we
have found, making them easier for maintainer to view, browse and
search. We also plan to include bugs found by other tools, such as
Sashiko.

Besides, I will send a patch to update the document.

> > >> Assisted-by: Codex:GPT-5.4
> > >> Co-developed-by: Nan Li <tonanli66@gmail.com>
> > >> Signed-off-by: Nan Li <tonanli66@gmail.com>
> > >> Signed-off-by: Yuqi Xu <xuyuqiabc@gmail.com>
> > >> Reviewed-by: Ren Wei <enjou1224z@gmail.com>
> > > Please take a look at
> > > https://docs.kernel.org/process/submitting-patches.html#sign-your-work-the-developer-s-certificate-of-origin
> > > and the sections that follow. What is listed here does not follow the
> > > rules.
>
> After reading this document, did you spot the second issues?
>
>       Andrew

Hi Andrew,

I have read through the document several times, but I am still not
sure what you mean by the second issue. Could you please clarify which
specific issue you are referring to?

At the moment, all of our patches are reviewed by Ren Wei before they
are sent out. Are you referring to that Ren Wei is not included with a
Signed-off-by tag? We did consider adding one, although I was
concerned that including too many tags might make the commit message
unnecessarily cluttered.

Thank you for the clarification.

Best,
Yuan

^ permalink raw reply

* [PATCH net v4 0/3] Fix to possible skb leak due to race condtion in tx path
From: Selvamani Rajagopal via B4 Relay @ 2026-07-21  2:20 UTC (permalink / raw)
  To: Parthiban Veerasooran, Andrew Lunn, Piergiorgio Beruto,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni
  Cc: netdev, linux-kernel, Andrew Lunn, Parthiban Veerasooran,
	Selvamani Rajagopal

Now the traffic is handled in threaded IRQ, and the
disable_traffic flag is checked before handling the
data, new race condition is exposed, in which
buffer may leak, if threaded IRQ interrupts the
trasmit path midway.

With this change, disable_traffic and waiting_tx_skb
pointer are protected by spin lock/unlock pair.

This is highlighted in Sashiko review
https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260611-level-trigger-v5-0-4533a9e85ce2%40onsemi.com

Also on buffer overrun condition, probably due to loss of
SPI data chunks, receive path doesn't see the expected
data chunk with end_valid bit set. As a result, driver
keeps adding data chunks to the skb before running out
of space and kernel panic is seen.

With this change, before adding data to the skb, if there
is no space, skb is freed and driver starts looking for
new frame by looking for a data chunk with start_valid
bit set.

[  705.405490] skbuff: skb_over_panic: text:ffffffd2eb72a264 len:1600 put:64 head:ffffff804e5cdc40 data:ffffff804e5cdc80 tail:0x680 end:0x640 dev:eth1
[  705.405569] ------------[ cut here ]------------
[  705.405575] kernel BUG at net/core/skbuff.c:214!
[  705.405589] Internal error: Oops - BUG: 00000000f2000800 [#1]  SMP

[ 6703.427690] Call trace:
[  705.925157]  skb_panic+0x58/0x68 (P)
[  705.928726]  skb_put+0x74/0x80
[  705.931772]  oa_tc6_update_rx_skb+0x44/0x98 [oa_tc6_mod]
[  705.937084]  oa_tc6_macphy_threaded_irq+0x3f4/0x900 [oa_tc6_mod]
[  705.943084]  irq_thread_fn+0x34/0xb8
[  705.946654]  irq_thread+0x1a0/0x300
[  705.950134]  kthread+0x138/0x150
[  705.953356]  ret_from_fork+0x10/0x20

Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
Changes in v4:
  - As disable_traffic means device is uselss unless
    driver re-loaded, all tx queues are turned off.
  - Process all the received chunks on buffer overflow,
    as long as data chunks doesn't have any error bits set
    in their footer.
  - Added spin lock protection in every place wait_tx_skb
    is used.
  - Carrier is not turned off on disable_traffic.

- Link to v3: https://lore.kernel.org/r/20260705-fix-race-condition-and-crash-v3-0-3e51841e4d08@onsemi.com

Changes in v3:
- Cover all the instances of disable_traffic flag with
  spin lock to serialize the access
- Disabling the tx queue and mark the carrier off when
  disable_traffic is set.
- Continue processing received chunks on buffer overflow
  error and "out of skb" error.
- Link to v2: https://lore.kernel.org/r/20260626-fix-race-condition-and-crash-v2-0-b6c5c10e604f@onsemi.com

Changes in v2:
- Improvment to how error -EAGAIN is handled. Took care of
  couple of use cases where start_bit and end_bit may be missing or
  repeated due to lost data chunks.
- Protected handling of waiting_tx_skb pointer with spin lock
- Link to v1: https://lore.kernel.org/r/20260621-fix-race-condition-and-crash-v1-0-87e290d9357f@onsemi.com

---
Selvamani Rajagopal (3):
      net: ethernet: oa_tc6: Protect skb pointer used by two different kernel instances
      net: ethernet: oa_tc6: Improvements to error recovery
      net: ethernet: oa_tc6: Disabled tx queues when disable_traffic is set

 drivers/net/ethernet/oa_tc6.c | 236 +++++++++++++++++++++++++++++++-----------
 1 file changed, 175 insertions(+), 61 deletions(-)
---
base-commit: 1c975de3343cdef506f2eecc833cc1f14b0401c4
change-id: 20260621-fix-race-condition-and-crash-94d055a665c4

Best regards,
-- 
Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>



^ permalink raw reply

* [PATCH net v4 1/3] net: ethernet: oa_tc6: Protect skb pointer used by two different kernel instances
From: Selvamani Rajagopal via B4 Relay @ 2026-07-21  2:20 UTC (permalink / raw)
  To: Parthiban Veerasooran, Andrew Lunn, Piergiorgio Beruto,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni
  Cc: netdev, linux-kernel, Andrew Lunn, Parthiban Veerasooran,
	Selvamani Rajagopal
In-Reply-To: <20260720-fix-race-condition-and-crash-v4-0-8273e2f38a1f@onsemi.com>

From: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>

Threaded IRQ uses waiting_tx_skb. Transmit path also uses
this pointer without any mutual exclusion protection. As a
result, it might leak skb buffer, particularly threaded IRQ
runs in the middle of tranmsmit path, near skb_linearize.

Fixes: b542d13fab0f ("net: ethernet: oa_tc6: Interrupt is active low, level triggered.")
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>

---
changes in v4
  - No change
changes in v3
  - Added the missed out spin lock protection for
    waiting_tx_skb and disable_traffic flag
changes in v2
  - added the missing prefix to the title
---
 drivers/net/ethernet/oa_tc6.c | 100 +++++++++++++++++++++++++++++-------------
 1 file changed, 70 insertions(+), 30 deletions(-)

diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6.c
index 0727d53345a3..5b24cce4f9b5 100644
--- a/drivers/net/ethernet/oa_tc6.c
+++ b/drivers/net/ethernet/oa_tc6.c
@@ -652,6 +652,26 @@ static int oa_tc6_enable_data_transfer(struct oa_tc6 *tc6)
 	return oa_tc6_write_register(tc6, OA_TC6_REG_CONFIG0, value);
 }
 
+/* Called when a frame that is meant to be transmitted, is dropped. */
+static void oa_tc6_drop_tx_skb(struct oa_tc6 *tc6, struct sk_buff *skb)
+{
+	if (skb) {
+		tc6->netdev->stats.tx_dropped++;
+		dev_kfree_skb_any(skb);
+	}
+}
+
+static struct sk_buff *oa_tc6_detach_waiting_tx_skb(struct oa_tc6 *tc6)
+{
+	struct sk_buff *skb;
+
+	lockdep_assert_held(&tc6->tx_skb_lock);
+	skb = tc6->waiting_tx_skb;
+	tc6->waiting_tx_skb = NULL;
+
+	return skb;
+}
+
 static void oa_tc6_cleanup_ongoing_rx_skb(struct oa_tc6 *tc6)
 {
 	if (tc6->rx_skb) {
@@ -663,26 +683,30 @@ static void oa_tc6_cleanup_ongoing_rx_skb(struct oa_tc6 *tc6)
 
 static void oa_tc6_cleanup_ongoing_tx_skb(struct oa_tc6 *tc6)
 {
-	if (tc6->ongoing_tx_skb) {
-		tc6->netdev->stats.tx_dropped++;
-		kfree_skb(tc6->ongoing_tx_skb);
-		tc6->ongoing_tx_skb = NULL;
-	}
+	oa_tc6_drop_tx_skb(tc6, tc6->ongoing_tx_skb);
+	tc6->ongoing_tx_skb = NULL;
 }
 
 static void oa_tc6_cleanup_waiting_tx_skb(struct oa_tc6 *tc6)
 {
-	if (tc6->waiting_tx_skb) {
-		tc6->netdev->stats.tx_dropped++;
-		kfree_skb(tc6->waiting_tx_skb);
-		tc6->waiting_tx_skb = NULL;
-	}
+	struct sk_buff *skb;
+
+	spin_lock_bh(&tc6->tx_skb_lock);
+	skb = oa_tc6_detach_waiting_tx_skb(tc6);
+	spin_unlock_bh(&tc6->tx_skb_lock);
+
+	oa_tc6_drop_tx_skb(tc6, skb);
 }
 
-static void oa_tc6_free_pending_skbs(struct oa_tc6 *tc6)
+static void oa_tc6_free_ongoing_skbs(struct oa_tc6 *tc6)
 {
 	oa_tc6_cleanup_ongoing_tx_skb(tc6);
 	oa_tc6_cleanup_ongoing_rx_skb(tc6);
+}
+
+static void oa_tc6_free_pending_skbs(struct oa_tc6 *tc6)
+{
+	oa_tc6_free_ongoing_skbs(tc6);
 	oa_tc6_cleanup_waiting_tx_skb(tc6);
 }
 
@@ -693,9 +717,15 @@ static void oa_tc6_free_pending_skbs(struct oa_tc6 *tc6)
 static void oa_tc6_disable_traffic(struct oa_tc6 *tc6)
 {
 	u32 regval = INT_MASK0_ALL_INTERRUPTS;
+	struct sk_buff *skb;
 
+	spin_lock_bh(&tc6->tx_skb_lock);
 	tc6->disable_traffic = true;
-	oa_tc6_free_pending_skbs(tc6);
+	skb = oa_tc6_detach_waiting_tx_skb(tc6);
+	spin_unlock_bh(&tc6->tx_skb_lock);
+
+	oa_tc6_drop_tx_skb(tc6, skb);
+	oa_tc6_free_ongoing_skbs(tc6);
 	oa_tc6_write_register(tc6, OA_TC6_REG_INT_MASK0, regval);
 	oa_tc6_read_register(tc6, OA_TC6_REG_STATUS0, &regval);
 	oa_tc6_write_register(tc6, OA_TC6_REG_STATUS0, regval);
@@ -1136,8 +1166,7 @@ static int oa_tc6_try_spi_transfer(struct oa_tc6 *tc6)
 			if (ret == -EAGAIN)
 				continue;
 
-			oa_tc6_cleanup_ongoing_tx_skb(tc6);
-			oa_tc6_cleanup_ongoing_rx_skb(tc6);
+			oa_tc6_free_ongoing_skbs(tc6);
 			netdev_err(tc6->netdev, "Device error: %d\n", ret);
 			return ret;
 		}
@@ -1159,15 +1188,20 @@ static irqreturn_t oa_tc6_macphy_threaded_irq(int irq, void *data)
 	 * no need to attempt spi transfer, once it fails. Pending skbs
 	 * are already freed.
 	 */
-	if (!tc6->disable_traffic) {
-		while (tc6->int_flag ||
-		       (tc6->waiting_tx_skb && tc6->tx_credits)) {
-			ret = oa_tc6_try_spi_transfer(tc6);
-			if (ret) {
-				disable_irq_nosync(tc6->spi->irq);
-				oa_tc6_disable_traffic(tc6);
-				break;
-			}
+	spin_lock_bh(&tc6->tx_skb_lock);
+	if (tc6->disable_traffic) {
+		spin_unlock_bh(&tc6->tx_skb_lock);
+		return IRQ_HANDLED;
+	}
+	spin_unlock_bh(&tc6->tx_skb_lock);
+
+	while (tc6->int_flag ||
+	       (tc6->waiting_tx_skb && tc6->tx_credits)) {
+		ret = oa_tc6_try_spi_transfer(tc6);
+		if (ret) {
+			disable_irq_nosync(tc6->spi->irq);
+			oa_tc6_disable_traffic(tc6);
+			break;
 		}
 	}
 
@@ -1250,18 +1284,22 @@ EXPORT_SYMBOL_GPL(oa_tc6_zero_align_receive_frame_enable);
  */
 netdev_tx_t oa_tc6_start_xmit(struct oa_tc6 *tc6, struct sk_buff *skb)
 {
-	if (tc6->disable_traffic || tc6->waiting_tx_skb) {
-		netif_stop_queue(tc6->netdev);
-		return NETDEV_TX_BUSY;
-	}
-
 	if (skb_linearize(skb)) {
-		dev_kfree_skb_any(skb);
-		tc6->netdev->stats.tx_dropped++;
+		oa_tc6_drop_tx_skb(tc6, skb);
 		return NETDEV_TX_OK;
 	}
 
 	spin_lock_bh(&tc6->tx_skb_lock);
+	if (tc6->waiting_tx_skb) {
+		netif_stop_queue(tc6->netdev);
+		spin_unlock_bh(&tc6->tx_skb_lock);
+		return NETDEV_TX_BUSY;
+	}
+	if (tc6->disable_traffic) {
+		spin_unlock_bh(&tc6->tx_skb_lock);
+		oa_tc6_drop_tx_skb(tc6, skb);
+		return NETDEV_TX_OK;
+	}
 	tc6->waiting_tx_skb = skb;
 	spin_unlock_bh(&tc6->tx_skb_lock);
 
@@ -1393,7 +1431,9 @@ EXPORT_SYMBOL_GPL(oa_tc6_init);
  */
 void oa_tc6_exit(struct oa_tc6 *tc6)
 {
+	spin_lock_bh(&tc6->tx_skb_lock);
 	tc6->disable_traffic = true;
+	spin_unlock_bh(&tc6->tx_skb_lock);
 	disable_irq(tc6->spi->irq);
 	oa_tc6_phy_exit(tc6);
 	oa_tc6_free_pending_skbs(tc6);

-- 
2.43.0



^ permalink raw reply related

* [PATCH net v4 2/3] net: ethernet: oa_tc6: Improvements to error recovery
From: Selvamani Rajagopal via B4 Relay @ 2026-07-21  2:20 UTC (permalink / raw)
  To: Parthiban Veerasooran, Andrew Lunn, Piergiorgio Beruto,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni
  Cc: netdev, linux-kernel, Andrew Lunn, Parthiban Veerasooran,
	Selvamani Rajagopal
In-Reply-To: <20260720-fix-race-condition-and-crash-v4-0-8273e2f38a1f@onsemi.com>

From: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>

When oversubscribed traffic causes lot of buffer overflow errors,
probably due to loss of data chunks, driver fails to find a
data chunk with end_valid bit set, before it runs out of sk buffer
space. As a result, assert is seen during skb_put.

Now check is made if tail + len > end, driver abandons the current
data and starts look for a data chunk with start_valid bit,
that is a new frame.

SK buffer allocation error is considered as recoverable error.

Fixes: d70a0d8f2f2d ("net: ethernet: oa_tc6: implement receive path to receive rx ethernet frames")
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>

---
changes in v4
  - rx_buf_overflow flag cleared, when end of frame and start of
    frame are handled in the same data chunk.
  - Added more comments to answer some of the AI review questions.
changes in v3
  - Continue processing more chunks on error code -EAGAIN. Previously
    we were bailing out.
changes in v2
  - Check rx_skb pointer before new allocation and NULL before use.
---
 drivers/net/ethernet/oa_tc6.c | 131 ++++++++++++++++++++++++++++++++----------
 1 file changed, 100 insertions(+), 31 deletions(-)

diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6.c
index 5b24cce4f9b5..9e6850ccfe6c 100644
--- a/drivers/net/ethernet/oa_tc6.c
+++ b/drivers/net/ethernet/oa_tc6.c
@@ -710,6 +710,12 @@ static void oa_tc6_free_pending_skbs(struct oa_tc6 *tc6)
 	oa_tc6_cleanup_waiting_tx_skb(tc6);
 }
 
+static void oa_tc6_look_for_new_frame(struct oa_tc6 *tc6)
+{
+	tc6->rx_buf_overflow = true;
+	oa_tc6_cleanup_ongoing_rx_skb(tc6);
+}
+
 /* If the failure is at SPI interface level, masking and clearing
  * the interrupt of the device won't work. Since SPI interrupt is
  * disabled, it should stop the repeated interrupts.
@@ -753,8 +759,7 @@ static int oa_tc6_process_extended_status(struct oa_tc6 *tc6)
 	}
 
 	if (FIELD_GET(STATUS0_RX_BUFFER_OVERFLOW_ERROR, value)) {
-		tc6->rx_buf_overflow = true;
-		oa_tc6_cleanup_ongoing_rx_skb(tc6);
+		oa_tc6_look_for_new_frame(tc6);
 		net_err_ratelimited("%s: Receive buffer overflow error\n",
 				    tc6->netdev->name);
 		return -EAGAIN;
@@ -780,6 +785,8 @@ static int oa_tc6_process_extended_status(struct oa_tc6 *tc6)
 
 static int oa_tc6_process_rx_chunk_footer(struct oa_tc6 *tc6, u32 footer)
 {
+	int ret = 0;
+
 	/* Process rx chunk footer for the following,
 	 * 1. tx credits
 	 * 2. errors if any from MAC-PHY
@@ -790,9 +797,11 @@ static int oa_tc6_process_rx_chunk_footer(struct oa_tc6 *tc6, u32 footer)
 					     footer);
 
 	if (FIELD_GET(OA_TC6_DATA_FOOTER_EXTENDED_STS, footer)) {
-		int ret = oa_tc6_process_extended_status(tc6);
-
-		if (ret)
+		ret = oa_tc6_process_extended_status(tc6);
+		/* EAGAIN error is recoverable. Move on to check
+		 * HEADER and SYNC errors before returning.
+		 */
+		if (ret && ret != -EAGAIN)
 			return ret;
 	}
 
@@ -810,7 +819,7 @@ static int oa_tc6_process_rx_chunk_footer(struct oa_tc6 *tc6, u32 footer)
 		return -ENODEV;
 	}
 
-	return 0;
+	return ret;
 }
 
 static void oa_tc6_submit_rx_skb(struct oa_tc6 *tc6)
@@ -835,13 +844,35 @@ static void oa_tc6_submit_rx_skb(struct oa_tc6 *tc6)
 	tc6->rx_skb = NULL;
 }
 
-static void oa_tc6_update_rx_skb(struct oa_tc6 *tc6, u8 *payload, u8 length)
+/* On oversubscribed traffic condition, particularly with overwhelming rx
+ * buffer overflow errors, there could be data chunk loss. If tail + length
+ * goes beyond end pointer, that is an indication that the data chunk with
+ * end_valid bit is lost. Time to look for a data chunk with start_valid bit.
+ *
+ * If rx_skb is NULL, it is time to start looking for data chunk with
+ * start_bit.
+ */
+static int oa_tc6_update_rx_skb(struct oa_tc6 *tc6, u8 *payload, u8 length)
 {
+	if (!tc6->rx_skb ||
+	    (tc6->rx_skb->tail + length) > tc6->rx_skb->end) {
+		oa_tc6_look_for_new_frame(tc6);
+		return -EAGAIN;
+	}
+
 	memcpy(skb_put(tc6->rx_skb, length), payload, length);
+	return 0;
 }
 
+/* On overwhelming rx buffer overflow errors, due to data chunk loss, it is
+ * possible that we get two data chunks with start_valid bit set, without
+ * end_valid bit set in between. In this case, rx_skb would have a valid
+ * buffer pointer. We should release, if a valid pointer is found before
+ * allocating a new one.
+ */
 static int oa_tc6_allocate_rx_skb(struct oa_tc6 *tc6)
 {
+	oa_tc6_cleanup_ongoing_rx_skb(tc6);
 	tc6->rx_skb = netdev_alloc_skb_ip_align(tc6->netdev, tc6->netdev->mtu +
 						ETH_HLEN + ETH_FCS_LEN);
 	if (!tc6->rx_skb) {
@@ -861,7 +892,9 @@ static int oa_tc6_prcs_complete_rx_frame(struct oa_tc6 *tc6, u8 *payload,
 	if (ret)
 		return ret;
 
-	oa_tc6_update_rx_skb(tc6, payload, size);
+	ret = oa_tc6_update_rx_skb(tc6, payload, size);
+	if (ret)
+		return ret;
 
 	oa_tc6_submit_rx_skb(tc6);
 
@@ -876,22 +909,24 @@ static int oa_tc6_prcs_rx_frame_start(struct oa_tc6 *tc6, u8 *payload, u16 size)
 	if (ret)
 		return ret;
 
-	oa_tc6_update_rx_skb(tc6, payload, size);
-
-	return 0;
+	return oa_tc6_update_rx_skb(tc6, payload, size);
 }
 
-static void oa_tc6_prcs_rx_frame_end(struct oa_tc6 *tc6, u8 *payload, u16 size)
+static int oa_tc6_prcs_rx_frame_end(struct oa_tc6 *tc6, u8 *payload, u16 size)
 {
-	oa_tc6_update_rx_skb(tc6, payload, size);
+	int ret;
 
-	oa_tc6_submit_rx_skb(tc6);
+	ret = oa_tc6_update_rx_skb(tc6, payload, size);
+	if (!ret)
+		oa_tc6_submit_rx_skb(tc6);
+	return ret;
 }
 
-static void oa_tc6_prcs_ongoing_rx_frame(struct oa_tc6 *tc6, u8 *payload,
-					 u32 footer)
+static int oa_tc6_prcs_ongoing_rx_frame(struct oa_tc6 *tc6, u8 *payload,
+					u32 footer)
 {
-	oa_tc6_update_rx_skb(tc6, payload, OA_TC6_CHUNK_PAYLOAD_SIZE);
+	return oa_tc6_update_rx_skb(tc6, payload,
+				    OA_TC6_CHUNK_PAYLOAD_SIZE);
 }
 
 static int oa_tc6_prcs_rx_chunk_payload(struct oa_tc6 *tc6, u8 *data,
@@ -931,8 +966,7 @@ static int oa_tc6_prcs_rx_chunk_payload(struct oa_tc6 *tc6, u8 *data,
 	/* Process the chunk with only rx frame end */
 	if (end_valid && !start_valid) {
 		size = end_byte_offset + 1;
-		oa_tc6_prcs_rx_frame_end(tc6, data, size);
-		return 0;
+		return oa_tc6_prcs_rx_frame_end(tc6, data, size);
 	}
 
 	/* Process the chunk with previous rx frame end and next rx frame
@@ -946,6 +980,14 @@ static int oa_tc6_prcs_rx_chunk_payload(struct oa_tc6 *tc6, u8 *data,
 		if (tc6->rx_skb) {
 			size = end_byte_offset + 1;
 			oa_tc6_prcs_rx_frame_end(tc6, data, size);
+
+			/* Purpose of rx_buf_overflow is make the
+			 * code to look for new frame. At this
+			 * stage, we have a new frame to process.
+			 * So, making it false, in case it is set
+			 * to true by oa_tc6_prcs_rx_frame_end.
+			 */
+			tc6->rx_buf_overflow = false;
 		}
 		size = OA_TC6_CHUNK_PAYLOAD_SIZE - start_byte_offset;
 		return oa_tc6_prcs_rx_frame_start(tc6,
@@ -954,9 +996,7 @@ static int oa_tc6_prcs_rx_chunk_payload(struct oa_tc6 *tc6, u8 *data,
 	}
 
 	/* Process the chunk with ongoing rx frame data */
-	oa_tc6_prcs_ongoing_rx_frame(tc6, data, footer);
-
-	return 0;
+	return oa_tc6_prcs_ongoing_rx_frame(tc6, data, footer);
 }
 
 static u32 oa_tc6_get_rx_chunk_footer(struct oa_tc6 *tc6, u16 footer_offset)
@@ -972,8 +1012,9 @@ static u32 oa_tc6_get_rx_chunk_footer(struct oa_tc6 *tc6, u16 footer_offset)
 static int oa_tc6_process_spi_data_rx_buf(struct oa_tc6 *tc6, u16 length)
 {
 	u16 no_of_rx_chunks = length / OA_TC6_CHUNK_SIZE;
+	bool retry = false;
+	int ret = 0;
 	u32 footer;
-	int ret;
 
 	/* All the rx chunks in the receive SPI data buffer are examined here */
 	for (int i = 0; i < no_of_rx_chunks; i++) {
@@ -982,8 +1023,11 @@ static int oa_tc6_process_spi_data_rx_buf(struct oa_tc6 *tc6, u16 length)
 						    OA_TC6_CHUNK_PAYLOAD_SIZE);
 
 		ret = oa_tc6_process_rx_chunk_footer(tc6, footer);
-		if (ret)
-			return ret;
+		if (ret) {
+			if (ret != -EAGAIN)
+				return ret;
+			retry = true;
+		}
 
 		/* If there is a data valid chunks then process it for the
 		 * information needed to determine the validity and the location
@@ -995,12 +1039,35 @@ static int oa_tc6_process_spi_data_rx_buf(struct oa_tc6 *tc6, u16 length)
 
 			ret = oa_tc6_prcs_rx_chunk_payload(tc6, payload,
 							   footer);
-			if (ret)
-				return ret;
+			if (ret) {
+				if (ret != -ENOMEM && ret != -EAGAIN)
+					return ret;
+				retry = true;
+			}
 		}
 	}
 
-	return 0;
+	/* Not bailing out on recoverable error codes, -EAGAIN and
+	 * -ENOMEM. If subsequent loop iterations, if any, succeeds,
+	 * error code would be overwritten. retry flag helps to
+	 * make the caller to continue and retry. Since recovery
+	 * action for -ENOMEM and -EAGAIN are same, we are returning
+	 * one of the error codes, that is -EAGAIN.
+	 *
+	 * Successful recovery depends on how small the frames are,
+	 * how many chunks, among the received chunks triggered the
+	 * error, whether data is intact even with error conditions.
+	 * As a result, there is no single, best method to recover
+	 * most data when error conditions hit. We do our best by
+	 * processing all the chunks with good "footer header" and
+	 * "data valid" bit set.
+	 */
+	if (retry) {
+		ret = -EAGAIN;
+		oa_tc6_look_for_new_frame(tc6);
+	}
+
+	return ret;
 }
 
 static __be32 oa_tc6_prepare_data_header(bool data_valid, bool start_valid,
@@ -1162,10 +1229,12 @@ static int oa_tc6_try_spi_transfer(struct oa_tc6 *tc6)
 		}
 
 		ret = oa_tc6_process_spi_data_rx_buf(tc6, spi_len);
-		if (ret) {
-			if (ret == -EAGAIN)
-				continue;
 
+		/* Not continuing with the next iteration to give
+		 * waiting_tx_skb a chance to get drained, if
+		 * needed.
+		 */
+		if (ret && ret != -EAGAIN) {
 			oa_tc6_free_ongoing_skbs(tc6);
 			netdev_err(tc6->netdev, "Device error: %d\n", ret);
 			return ret;

-- 
2.43.0



^ permalink raw reply related

* [PATCH net v4 3/3] net: ethernet: oa_tc6: Disabled tx queues when disable_traffic is set
From: Selvamani Rajagopal via B4 Relay @ 2026-07-21  2:20 UTC (permalink / raw)
  To: Parthiban Veerasooran, Andrew Lunn, Piergiorgio Beruto,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni
  Cc: netdev, linux-kernel, Andrew Lunn, Parthiban Veerasooran,
	Selvamani Rajagopal
In-Reply-To: <20260720-fix-race-condition-and-crash-v4-0-8273e2f38a1f@onsemi.com>

From: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>

Previously, TX queue interface was stopped when
disable_traffic flag was set. It is more appropriate to
disable the queue as there is no recovery, once
disable_traffic is set. Carrier is also marked off

Fixes: b542d13fab0f ("net: ethernet: oa_tc6: Interrupt is active low, level triggered.")
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>

changes in v4
  - Reverted the the statement that turned carrier off on
    disble_traffic, as it may have side effects
changes in v3
  - New patch. Carrier marked off once disable_traffic is set
---
 drivers/net/ethernet/oa_tc6.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6.c
index 9e6850ccfe6c..4b8f6280be51 100644
--- a/drivers/net/ethernet/oa_tc6.c
+++ b/drivers/net/ethernet/oa_tc6.c
@@ -730,6 +730,11 @@ static void oa_tc6_disable_traffic(struct oa_tc6 *tc6)
 	skb = oa_tc6_detach_waiting_tx_skb(tc6);
 	spin_unlock_bh(&tc6->tx_skb_lock);
 
+	/* disable_traffic, when set, is a point of no
+	 * return to working state. TX queues are
+	 * disabled.
+	 */
+	netif_tx_disable(tc6->netdev);
 	oa_tc6_drop_tx_skb(tc6, skb);
 	oa_tc6_free_ongoing_skbs(tc6);
 	oa_tc6_write_register(tc6, OA_TC6_REG_INT_MASK0, regval);

-- 
2.43.0



^ permalink raw reply related

* [PATCH net-next v10 0/5] Add support for RTL8261
From: javen @ 2026-07-21  2:24 UTC (permalink / raw)
  To: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni,
	freddy_gu, nb, maxime.chevallier
  Cc: netdev, linux-kernel, daniel, vladimir.oltean, Javen Xu

From: Javen Xu <javen_xu@realsil.com.cn>

Add support for RTL8261C/D and add support for loading firmware.

Javen Xu (5):
  net: phy: c45: add genphy_c45_pma_soft_reset()
  net: phy: c45: add setup and read master/slave helpers
  net: phy: realtek: add support for RTL8261C_CG
  net: phy: realtek: load firmware for RTL8261C_CG
  net: phy: realtek: add support for RTL8261D

 drivers/net/phy/phy-c45.c              | 125 ++++++++
 drivers/net/phy/realtek/realtek_main.c | 411 +++++++++++++++++++++++++
 include/linux/phy.h                    |   1 +
 include/uapi/linux/mdio.h              |   5 +
 4 files changed, 542 insertions(+)

-- 
2.43.0


^ permalink raw reply

* [PATCH net-next v10 1/5] net: phy: c45: add genphy_c45_pma_soft_reset()
From: javen @ 2026-07-21  2:24 UTC (permalink / raw)
  To: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni,
	freddy_gu, nb, maxime.chevallier
  Cc: netdev, linux-kernel, daniel, vladimir.oltean, Javen Xu
In-Reply-To: <20260721022410.1391-1-javen_xu@realsil.com.cn>

From: Javen Xu <javen_xu@realsil.com.cn>

Add a generic Clause 45 software reset helper. The helper sets the reset
bit in the PMA/PMD control register and waits until the bit is cleared by
hardware.

Reviewed-by: Nicolai Buchwitz <nb@tipi-net.de>
Signed-off-by: Javen Xu <javen_xu@realsil.com.cn>
---
Changes in v2:
 - no changes, new file

Changes in v3:
 - re-order function according to the order in phy-c45.c

Changes in v4:
 - no changes

Changes in v5:
 - no changes

Changes in v6:
 - increase timeout to 600ms

Changes in v7:
 - no changes

Changes in v8:
 - no changes

Changes in v9:
 - rename genphy_c45_soft_reset to genphy_c45_pma_soft_reset

Changes in v10:
 - no change. Netdev queue overflown. Resend.
---
 drivers/net/phy/phy-c45.c | 22 ++++++++++++++++++++++
 include/linux/phy.h       |  1 +
 2 files changed, 23 insertions(+)

diff --git a/drivers/net/phy/phy-c45.c b/drivers/net/phy/phy-c45.c
index 126951741428..c1f817a59739 100644
--- a/drivers/net/phy/phy-c45.c
+++ b/drivers/net/phy/phy-c45.c
@@ -384,6 +384,28 @@ int genphy_c45_check_and_restart_aneg(struct phy_device *phydev, bool restart)
 }
 EXPORT_SYMBOL_GPL(genphy_c45_check_and_restart_aneg);
 
+/**
+ * genphy_c45_pma_soft_reset - software reset the PHY via Clause 45 PMA/PMD control register
+ * @phydev: target phy_device struct
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
+int genphy_c45_pma_soft_reset(struct phy_device *phydev)
+{
+	int ret, val;
+
+	ret = phy_set_bits_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_CTRL1,
+			       MDIO_CTRL1_RESET);
+	if (ret < 0)
+		return ret;
+
+	return phy_read_mmd_poll_timeout(phydev, MDIO_MMD_PMAPMD,
+					 MDIO_CTRL1, val,
+					 !(val & MDIO_CTRL1_RESET),
+					 5000, 600000, true);
+}
+EXPORT_SYMBOL_GPL(genphy_c45_pma_soft_reset);
+
 /**
  * genphy_c45_aneg_done - return auto-negotiation complete status
  * @phydev: target phy_device struct
diff --git a/include/linux/phy.h b/include/linux/phy.h
index beff1d6fcc7c..57cf9ac3524d 100644
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -2314,6 +2314,7 @@ int genphy_c37_read_status(struct phy_device *phydev, bool *changed);
 /* Clause 45 PHY */
 int genphy_c45_restart_aneg(struct phy_device *phydev);
 int genphy_c45_check_and_restart_aneg(struct phy_device *phydev, bool restart);
+int genphy_c45_pma_soft_reset(struct phy_device *phydev);
 int genphy_c45_aneg_done(struct phy_device *phydev);
 int genphy_c45_read_link(struct phy_device *phydev);
 int genphy_c45_read_lpa(struct phy_device *phydev);
-- 
2.43.0


^ permalink raw reply related

* [PATCH net-next v10 2/5] net: phy: c45: add setup and read master/slave helpers
From: javen @ 2026-07-21  2:24 UTC (permalink / raw)
  To: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni,
	freddy_gu, nb, maxime.chevallier
  Cc: netdev, linux-kernel, daniel, vladimir.oltean, Javen Xu
In-Reply-To: <20260721022410.1391-1-javen_xu@realsil.com.cn>

From: Javen Xu <javen_xu@realsil.com.cn>

This patch adds two static helpers in drivers/net/phy/phy-c45.c to
configure and read back master-slave roles for non BASE-T1 Clause 45
PHYs via the 10GBASE-T AN control/status registers.
These helpers are wired into genphy_c45_config_aneg() and
genphy_c45_read_status(). This changes the observable ethtool output
for drivers using the generic c45 read path.

Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: Javen Xu <javen_xu@realsil.com.cn>
---
Changes in v2:
 - no changes, new file

Changes in v3:
 - re-order function according to the order in phy-c45.c
 - add kernel-doc about return value
 - add MASTER_SLAVE_CFG_MASTER_PREFERRED,
   MASTER_SLAVE_CFG_SLAVE_PREFERRED, MASTER_SLAVE_CFG_UNKNOWN,
   MASTER_SLAVE_CFG_UNSUPPORTED, MASTER_SLAVE_CFG_SLAVE_PREFERRED cfg

Changes in v4:
 - no changes

Changes in v5:
 - move genphy_c45_an_setup_master_slave() to genphy_c45_config_aneg(),
   as that C22 does.

Changes in v6:
 - add colon in the function description
 - add genphy_c45_read_master_slave in read function

Changes in v7:
 - when phydev->link is down, just return UNKNOWN
 - modify commit message

Changes in v8:
 - no changes

Changes in v9:
 - no changes

Changes in v10:
 - no changes. Netdev queue overflow. Resend.
---
 drivers/net/phy/phy-c45.c | 103 ++++++++++++++++++++++++++++++++++++++
 include/uapi/linux/mdio.h |   5 ++
 2 files changed, 108 insertions(+)

diff --git a/drivers/net/phy/phy-c45.c b/drivers/net/phy/phy-c45.c
index c1f817a59739..870920311f9a 100644
--- a/drivers/net/phy/phy-c45.c
+++ b/drivers/net/phy/phy-c45.c
@@ -406,6 +406,97 @@ int genphy_c45_pma_soft_reset(struct phy_device *phydev)
 }
 EXPORT_SYMBOL_GPL(genphy_c45_pma_soft_reset);
 
+/**
+ * genphy_c45_an_setup_master_slave - Configure Master/Slave setting for C45 PHYs
+ * @phydev: target phy_device struct
+ *
+ * Description: Configure the forced or preferred Master/Slave role
+ * 10GBASE-T control register (MMD 7, Register 0x0020) according to
+ * IEEE 802.3 standards.
+ *
+ * Return: negative errno code on failure, 0 if Master/Slave didn't change,
+ * or 1 if Master/Slave modes changed.
+ */
+static int genphy_c45_an_setup_master_slave(struct phy_device *phydev)
+{
+	u16 ctl = 0;
+
+	switch (phydev->master_slave_set) {
+	case MASTER_SLAVE_CFG_MASTER_PREFERRED:
+		ctl = MDIO_AN_10GBT_CTRL_MS_PORT_TYPE;
+		break;
+	case MASTER_SLAVE_CFG_SLAVE_PREFERRED:
+		break;
+	case MASTER_SLAVE_CFG_MASTER_FORCE:
+		ctl = MDIO_AN_10GBT_CTRL_MS_ENABLE | MDIO_AN_10GBT_CTRL_MS_VALUE;
+		break;
+	case MASTER_SLAVE_CFG_SLAVE_FORCE:
+		ctl = MDIO_AN_10GBT_CTRL_MS_ENABLE;
+		break;
+	case MASTER_SLAVE_CFG_UNKNOWN:
+	case MASTER_SLAVE_CFG_UNSUPPORTED:
+		return 0;
+	default:
+		phydev_warn(phydev, "Unsupported Master/Slave mode\n");
+		return -EOPNOTSUPP;
+	}
+
+	return phy_modify_mmd_changed(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_CTRL,
+				      MDIO_AN_10GBT_CTRL_MS_ENABLE |
+				      MDIO_AN_10GBT_CTRL_MS_VALUE |
+				      MDIO_AN_10GBT_CTRL_MS_PORT_TYPE, ctl);
+}
+
+/**
+ * genphy_c45_read_master_slave - read master/slave status
+ * @phydev: target phy_device struct
+ *
+ * Description: Read the Master/Slave configuration and status
+ * from 10GBASE-T control/status registers (MMD 7, Reg 0x0020 and 0x0021).
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+static int genphy_c45_read_master_slave(struct phy_device *phydev)
+{
+	int val;
+
+	phydev->master_slave_get = MASTER_SLAVE_CFG_UNKNOWN;
+	phydev->master_slave_state = MASTER_SLAVE_STATE_UNKNOWN;
+
+	val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_CTRL);
+	if (val < 0)
+		return val;
+
+	if (val & MDIO_AN_10GBT_CTRL_MS_ENABLE) {
+		if (val & MDIO_AN_10GBT_CTRL_MS_VALUE)
+			phydev->master_slave_get = MASTER_SLAVE_CFG_MASTER_FORCE;
+		else
+			phydev->master_slave_get = MASTER_SLAVE_CFG_SLAVE_FORCE;
+	} else {
+		if (val & MDIO_AN_10GBT_CTRL_MS_PORT_TYPE)
+			phydev->master_slave_get = MASTER_SLAVE_CFG_MASTER_PREFERRED;
+		else
+			phydev->master_slave_get = MASTER_SLAVE_CFG_SLAVE_PREFERRED;
+	}
+
+	val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_STAT);
+	if (val < 0)
+		return val;
+
+	if (val & MDIO_AN_10GBT_STAT_MS_FAULT) {
+		phydev->master_slave_state = MASTER_SLAVE_STATE_ERR;
+	} else if (phydev->link) {
+		if (val & MDIO_AN_10GBT_STAT_MS_RES)
+			phydev->master_slave_state = MASTER_SLAVE_STATE_MASTER;
+		else
+			phydev->master_slave_state = MASTER_SLAVE_STATE_SLAVE;
+	} else {
+		phydev->master_slave_state = MASTER_SLAVE_STATE_UNKNOWN;
+	}
+
+	return 0;
+}
+
 /**
  * genphy_c45_aneg_done - return auto-negotiation complete status
  * @phydev: target phy_device struct
@@ -1214,6 +1305,10 @@ int genphy_c45_read_status(struct phy_device *phydev)
 			ret = genphy_c45_baset1_read_status(phydev);
 			if (ret < 0)
 				return ret;
+		} else {
+			ret = genphy_c45_read_master_slave(phydev);
+			if (ret < 0)
+				return ret;
 		}
 
 		phy_resolve_aneg_linkmode(phydev);
@@ -1247,6 +1342,14 @@ int genphy_c45_config_aneg(struct phy_device *phydev)
 	if (ret > 0)
 		changed = true;
 
+	if (!genphy_c45_baset1_able(phydev)) {
+		ret = genphy_c45_an_setup_master_slave(phydev);
+		if (ret < 0)
+			return ret;
+		if (ret > 0)
+			changed = true;
+	}
+
 	return genphy_c45_check_and_restart_aneg(phydev, changed);
 }
 EXPORT_SYMBOL_GPL(genphy_c45_config_aneg);
diff --git a/include/uapi/linux/mdio.h b/include/uapi/linux/mdio.h
index b2541c948fc1..06f4bc3c20c7 100644
--- a/include/uapi/linux/mdio.h
+++ b/include/uapi/linux/mdio.h
@@ -332,8 +332,13 @@
 #define MDIO_AN_10GBT_CTRL_ADV2_5G	0x0080	/* Advertise 2.5GBASE-T */
 #define MDIO_AN_10GBT_CTRL_ADV5G	0x0100	/* Advertise 5GBASE-T */
 #define MDIO_AN_10GBT_CTRL_ADV10G	0x1000	/* Advertise 10GBASE-T */
+#define MDIO_AN_10GBT_CTRL_MS_ENABLE	0x8000	/* Master/slave manual config enable */
+#define MDIO_AN_10GBT_CTRL_MS_VALUE	0x4000	/* Master/slave config value (1=Master) */
+#define MDIO_AN_10GBT_CTRL_MS_PORT_TYPE	0x2000	/* Master Preferred Type */
 
 /* AN 10GBASE-T status register. */
+#define MDIO_AN_10GBT_STAT_MS_FAULT	0x8000	/* Master/slave fault */
+#define MDIO_AN_10GBT_STAT_MS_RES	0x4000	/* Master/slave resolution (1=Master) */
 #define MDIO_AN_10GBT_STAT_LP2_5G	0x0020  /* LP is 2.5GBT capable */
 #define MDIO_AN_10GBT_STAT_LP5G		0x0040  /* LP is 5GBT capable */
 #define MDIO_AN_10GBT_STAT_LPTRR	0x0200	/* LP training reset req. */
-- 
2.43.0


^ permalink raw reply related

* [PATCH net-next v10 3/5] net: phy: realtek: add support for RTL8261C_CG
From: javen @ 2026-07-21  2:24 UTC (permalink / raw)
  To: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni,
	freddy_gu, nb, maxime.chevallier
  Cc: netdev, linux-kernel, daniel, vladimir.oltean, Javen Xu
In-Reply-To: <20260721022410.1391-1-javen_xu@realsil.com.cn>

From: Javen Xu <javen_xu@realsil.com.cn>

This patch adds support for Realtek phy chip RTL8261C_CG. Its PHY ID is
0x001cc898.
This patch introduces a distinct family of handlers (probe, get_features,
config_aneg, read_status, config_intr, handle_interrupt).

Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Reviewed-by: Nicolai Buchwitz <nb@tipi-net.de>
Signed-off-by: Javen Xu <javen_xu@realsil.com.cn>
---
Changes in v2:
 - no changes, new file

Changes in v3:
 - re-order function according to the order in phy-c45.c
 - add kernel-doc about return value
 - add MASTER_SLAVE_CFG_MASTER_PREFERRED,
   MASTER_SLAVE_CFG_SLAVE_PREFERRED, MASTER_SLAVE_CFG_UNKNOWN,
   MASTER_SLAVE_CFG_UNSUPPORTED, MASTER_SLAVE_CFG_SLAVE_PREFERRED cfg

Changes in v4:
 - no changes

Changes in v5:
 - remove genphy_c45_pma_setup_forced() for this is already done when
   calling genphy_c45_config_aneg()

Changes in v6:
 - when PHY_INTERRUPT_DISABLE, clear IMR and ISR
 - if AUTONEG_DISABLE, nothing need to do in rtl8261x_config_aneg
 - add rtl8261x_read_status, support 1G speed

Changes in v7:
 - remove RTL8261X_IMR and RTL8261X_ISR, duplicated definition
 - modify commit message
 - continue with default behavior when meet unknown sub_phy_id
 - change the internal order of rtl8261x_read_status
 - expand RTL8261X_INT_MASK_DEFAULT
 - add the handle for ADVERTISE_1000HALF in rtl8261x_config_aneg

Changes in v8:
 - no changes

Changes in v9:
 - get an status from genphy_c45_aneg_done()

Changes in v10:
 - no changes. Netdev queue overflow. Resend.
---
 drivers/net/phy/realtek/realtek_main.c | 192 +++++++++++++++++++++++++
 1 file changed, 192 insertions(+)

diff --git a/drivers/net/phy/realtek/realtek_main.c b/drivers/net/phy/realtek/realtek_main.c
index b65d0f5fa1a0..e09bff76e1dc 100644
--- a/drivers/net/phy/realtek/realtek_main.c
+++ b/drivers/net/phy/realtek/realtek_main.c
@@ -141,6 +141,10 @@
 #define RTL8211F_PHYSICAL_ADDR_WORD1		17
 #define RTL8211F_PHYSICAL_ADDR_WORD2		18
 
+#define RTL8261X_EXT_ADDR_REG			0xa436
+#define RTL8261X_EXT_DATA_REG			0xa438
+#define RTL_8261X_SUB_PHY_ID_ADDR		0x801d
+
 #define RTL822X_VND1_SERDES_OPTION			0x697a
 #define RTL822X_VND1_SERDES_OPTION_MODE_MASK		GENMASK(5, 0)
 #define RTL822X_VND1_SERDES_OPTION_MODE_2500BASEX_SGMII		0
@@ -251,6 +255,32 @@
 #define RTL_8221B_VM_CG				0x001cc84a
 #define RTL_8251B				0x001cc862
 #define RTL_8261C				0x001cc890
+#define RTL_8261C_CG				0x001cc898
+
+#define RTL8261C_CE_MODEL		0x00
+#define RTL8261X_INT_AUTONEG_ERROR	BIT(0)
+#define RTL8261X_INT_PAGE_RECV		BIT(2)
+#define RTL8261X_INT_AUTONEG_DONE	BIT(3)
+#define RTL8261X_INT_LINK_CHG		BIT(4)
+#define RTL8261X_INT_PHY_REG_ACCESS	BIT(5)
+#define RTL8261X_INT_PME		BIT(7)
+#define RTL8261X_INT_ALDPS_CHG		BIT(9)
+#define RTL8261X_INT_JABBER		BIT(10)
+
+#define RTL8261X_INT_MASK_DEFAULT	(RTL8261X_INT_AUTONEG_DONE | \
+					 RTL8261X_INT_LINK_CHG | \
+					 RTL8261X_INT_AUTONEG_ERROR | \
+					 RTL8261X_INT_JABBER)
+
+#define RTL8261X_INT_MASK_ALL		(RTL8261X_INT_AUTONEG_ERROR | \
+					 RTL8261X_INT_PAGE_RECV | \
+					 RTL8261X_INT_AUTONEG_DONE | \
+					 RTL8261X_INT_LINK_CHG | \
+					 RTL8261X_INT_PHY_REG_ACCESS | \
+					 RTL8261X_INT_PME | \
+					 RTL8261X_INT_ALDPS_CHG | \
+					 RTL8261X_INT_JABBER)
+
 
 /* RTL8211E and RTL8211F support up to three LEDs */
 #define RTL8211x_LED_COUNT			3
@@ -310,6 +340,156 @@ static int rtl821x_modify_ext_page(struct phy_device *phydev, u16 ext_page,
 	return phy_restore_page(phydev, oldpage, ret);
 }
 
+static int rtl8261x_probe(struct phy_device *phydev)
+{
+	int sub_phy_id, ret;
+
+	ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, RTL8261X_EXT_ADDR_REG,
+			    RTL_8261X_SUB_PHY_ID_ADDR);
+	if (ret < 0)
+		return ret;
+
+	ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL8261X_EXT_DATA_REG);
+	if (ret < 0)
+		return ret;
+
+	sub_phy_id = (ret >> 8) & 0xff;
+
+	switch (sub_phy_id) {
+	case RTL8261C_CE_MODEL:
+		phydev_info(phydev, "RTL8261C detected (sub_id 0x%02x)\n", sub_phy_id);
+		break;
+
+	default:
+		phydev_warn(phydev, "Unknown sub_id 0x%02x, default behavior\n", sub_phy_id);
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static int rtl8261x_get_features(struct phy_device *phydev)
+{
+	int ret;
+
+	ret = genphy_c45_pma_read_abilities(phydev);
+	if (ret)
+		return ret;
+	/*
+	 * Supplement Multi-Gig speeds that may not be automatically detected
+	 * RTL8261X supports 2.5G/5G in addition to standard 10G
+	 */
+	linkmode_set_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
+			 phydev->supported);
+	linkmode_set_bit(ETHTOOL_LINK_MODE_5000baseT_Full_BIT,
+			 phydev->supported);
+
+	return 0;
+}
+
+static int rtl8261x_read_status(struct phy_device *phydev)
+{
+	int ret, val = 0;
+
+	if (phydev->autoneg == AUTONEG_ENABLE) {
+		ret = genphy_c45_aneg_done(phydev);
+		if (ret < 0)
+			return ret;
+
+		if (ret) {
+			val = phy_read_mmd(phydev, MDIO_MMD_VEND2,
+					   RTL822X_VND2_C22_REG(MII_STAT1000));
+			if (val < 0)
+				return val;
+		}
+	}
+
+	mii_stat1000_mod_linkmode_lpa_t(phydev->lp_advertising, val);
+
+	ret = genphy_c45_read_status(phydev);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int rtl8261x_config_intr(struct phy_device *phydev)
+{
+	int ret;
+
+	if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+		ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL8221B_VND2_INSR);
+		if (ret < 0)
+			return ret;
+
+		ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, RTL8221B_VND2_INER,
+				    RTL8261X_INT_MASK_DEFAULT);
+		if (ret < 0)
+			return ret;
+	} else {
+		ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, RTL8221B_VND2_INER, 0);
+		if (ret < 0)
+			return ret;
+
+		ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL8221B_VND2_INSR);
+		if (ret < 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+static irqreturn_t rtl8261x_handle_interrupt(struct phy_device *phydev)
+{
+	int irq_status;
+
+	irq_status = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL8221B_VND2_INSR);
+	if (irq_status < 0) {
+		phy_error(phydev);
+		return IRQ_NONE;
+	}
+
+	if (!(irq_status & RTL8261X_INT_MASK_ALL))
+		return IRQ_NONE;
+
+	if (irq_status & (RTL8261X_INT_LINK_CHG | RTL8261X_INT_AUTONEG_DONE |
+	    RTL8261X_INT_AUTONEG_ERROR | RTL8261X_INT_JABBER))
+		phy_trigger_machine(phydev);
+
+	return IRQ_HANDLED;
+}
+
+static int rtl8261x_config_aneg(struct phy_device *phydev)
+{
+	u16 adv_1g = 0;
+	int ret;
+
+	ret = genphy_c45_config_aneg(phydev);
+	if (ret < 0)
+		return ret;
+
+	if (phydev->autoneg == AUTONEG_DISABLE)
+		return 0;
+
+	if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
+			      phydev->advertising))
+		adv_1g = ADVERTISE_1000FULL;
+	if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
+			      phydev->advertising))
+		adv_1g |= ADVERTISE_1000HALF;
+
+	ret = phy_modify_mmd_changed(phydev, MDIO_MMD_VEND2,
+				     RTL822X_VND2_C22_REG(MII_CTRL1000),
+				     ADVERTISE_1000FULL | ADVERTISE_1000HALF,
+				     adv_1g);
+	if (ret < 0)
+		return ret;
+	if (ret > 0)
+		return genphy_c45_restart_aneg(phydev);
+
+	return 0;
+}
+
 static int rtl821x_probe(struct phy_device *phydev)
 {
 	struct device *dev = &phydev->mdio.dev;
@@ -3002,6 +3182,18 @@ static struct phy_driver realtek_drvs[] = {
 		.resume		= genphy_resume,
 		.read_mmd	= genphy_read_mmd_unsupported,
 		.write_mmd	= genphy_write_mmd_unsupported,
+	}, {
+		PHY_ID_MATCH_EXACT(RTL_8261C_CG),
+		.name			= "Realtek RTL8261C 10Gbps PHY",
+		.probe			= rtl8261x_probe,
+		.get_features		= rtl8261x_get_features,
+		.config_aneg		= rtl8261x_config_aneg,
+		.read_status		= rtl8261x_read_status,
+		.config_intr		= rtl8261x_config_intr,
+		.handle_interrupt	= rtl8261x_handle_interrupt,
+		.soft_reset		= genphy_c45_pma_soft_reset,
+		.suspend		= genphy_c45_pma_suspend,
+		.resume			= genphy_c45_pma_resume,
 	},
 };
 
-- 
2.43.0


^ permalink raw reply related

* [PATCH net-next v10 5/5] net: phy: realtek: add support for RTL8261D
From: javen @ 2026-07-21  2:24 UTC (permalink / raw)
  To: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni,
	freddy_gu, nb, maxime.chevallier
  Cc: netdev, linux-kernel, daniel, vladimir.oltean, Javen Xu
In-Reply-To: <20260721022410.1391-1-javen_xu@realsil.com.cn>

From: Javen Xu <javen_xu@realsil.com.cn>

RTL8261D is also 10g phy. It's sub_phy_id is 0x81. And it does not need
any firmware.

Signed-off-by: Javen Xu <javen_xu@realsil.com.cn>
---
Changes in v10:
 - no changes. New file.
---
 drivers/net/phy/realtek/realtek_main.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/drivers/net/phy/realtek/realtek_main.c b/drivers/net/phy/realtek/realtek_main.c
index ba218559f39c..1c0b9c40a81e 100644
--- a/drivers/net/phy/realtek/realtek_main.c
+++ b/drivers/net/phy/realtek/realtek_main.c
@@ -260,6 +260,7 @@
 #define RTL_8261C_CG				0x001cc898
 
 #define RTL8261C_CE_MODEL		0x00
+#define RTL8261D_MODEL			0x81
 #define RTL8261X_INT_AUTONEG_ERROR	BIT(0)
 #define RTL8261X_INT_PAGE_RECV		BIT(2)
 #define RTL8261X_INT_AUTONEG_DONE	BIT(3)
@@ -413,6 +414,10 @@ static int rtl8261x_probe(struct phy_device *phydev)
 		phydev_info(phydev, "RTL8261C detected (sub_id 0x%02x)\n", sub_phy_id);
 		break;
 
+	case RTL8261D_MODEL:
+		phydev_info(phydev, "RTL8261D detected (sub_id 0x%02x)\n", sub_phy_id);
+		break;
+
 	default:
 		phydev_warn(phydev, "Unknown sub_id 0x%02x, default behavior\n", sub_phy_id);
 		return -ENODEV;
@@ -3397,7 +3402,7 @@ static struct phy_driver realtek_drvs[] = {
 		.write_mmd	= genphy_write_mmd_unsupported,
 	}, {
 		PHY_ID_MATCH_EXACT(RTL_8261C_CG),
-		.name			= "Realtek RTL8261C 10Gbps PHY",
+		.name			= "Realtek RTL8261 10Gbps PHY",
 		.probe			= rtl8261x_probe,
 		.config_init		= rtl8261x_config_init,
 		.get_features		= rtl8261x_get_features,
-- 
2.43.0


^ permalink raw reply related

* [PATCH net-next v10 4/5] net: phy: realtek: load firmware for RTL8261C_CG
From: javen @ 2026-07-21  2:24 UTC (permalink / raw)
  To: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni,
	freddy_gu, nb, maxime.chevallier
  Cc: netdev, linux-kernel, daniel, vladimir.oltean, Javen Xu
In-Reply-To: <20260721022410.1391-1-javen_xu@realsil.com.cn>

From: Javen Xu <javen_xu@realsil.com.cn>

This patch adds support for loading firmware. Download some parameters
for RTL8261C_CG.

Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: Javen Xu <javen_xu@realsil.com.cn>
---
Changes in v2:
 - remove __pack, struct rtl8261x_fw_header and rtl8261x_fw_entry will not pad
 - reverse xmas tree for some definition
 - add explanation on rtl_phy_write_mmd_bits()

Changes in v3:
 - add struct rtl8261x_priv

Changes in v4:
 - add struct device *dev

Changes in v5:
 - no changes

Changes in v6:
 - replace rtl_phy_write_mmd_bits with phy_modify_mmd, keep mdio lock
 - check msb and lsb at the beginning of rtl8261x_fw_execute_entry()
 - add comments on rtl8261x_config_init()

Changes in v7:
 - no changes

Changes in v8:
 - remove some phydev_err message in rtl8261x_fw_execute_entry() and
   rtl8261x_config_init()

Changes in v9:
 - no changes

Changes in v10:
 - no changes. Netdev queue overflow. Resend.
---
 drivers/net/phy/realtek/realtek_main.c | 214 +++++++++++++++++++++++++
 1 file changed, 214 insertions(+)

diff --git a/drivers/net/phy/realtek/realtek_main.c b/drivers/net/phy/realtek/realtek_main.c
index e09bff76e1dc..ba218559f39c 100644
--- a/drivers/net/phy/realtek/realtek_main.c
+++ b/drivers/net/phy/realtek/realtek_main.c
@@ -8,7 +8,9 @@
  * Copyright (c) 2004 Freescale Semiconductor, Inc.
  */
 #include <linux/bitops.h>
+#include <linux/crc32.h>
 #include <linux/ethtool_netlink.h>
+#include <linux/firmware.h>
 #include <linux/of.h>
 #include <linux/phy.h>
 #include <linux/pm_wakeirq.h>
@@ -281,6 +283,43 @@
 					 RTL8261X_INT_ALDPS_CHG | \
 					 RTL8261X_INT_JABBER)
 
+#define FW_MAIN_MAGIC			0x52544C38
+#define FW_SUB_MAGIC_8261C		0x32363143
+#define RTL8261X_POLL_TIMEOUT_MS	100
+#define RTL8261X_MAX_MMD_DEV		31
+
+#define RTL8261C_CE_FW_NAME	"rtl_nic/rtl8261c.bin"
+MODULE_FIRMWARE(RTL8261C_CE_FW_NAME);
+
+enum rtl8261x_fw_op {
+	OP_WRITE = 0x00,	/* Write */
+	OP_POLL  = 0x02,	/* Polling */
+};
+
+struct rtl8261x_fw_header {
+	__le32 main_magic;	/* Main magic number */
+	__le32 sub_magic;	/* Sub magic number */
+	__le16 version_major;	/* Major version */
+	__le16 version_minor;	/* Minor version */
+	__le16 num_entries;	/* Number of entries */
+	__le16 reserved;	/* Reserved */
+	__le32 crc32;		/* CRC32 checksum */
+};
+
+struct rtl8261x_fw_entry {
+	__u8  type;		/* Operation type (OP_*) */
+	__u8  dev;		/* MMD device */
+	__le16 addr;		/* Register address */
+	__u8  msb;		/* MSB bit position */
+	__u8  lsb;		/* LSB bit position */
+	__le16 value;		/* Value to write/compare */
+	__le16 timeout_ms;	/* Poll timeout in milliseconds */
+	__u8  poll_set;		/* Poll until equal (1) or not equal (0) */
+	__u8  reserved;		/* Reserved */
+};
+
+#define FW_HEADER_SIZE		sizeof(struct rtl8261x_fw_header)
+#define FW_ENTRY_SIZE		sizeof(struct rtl8261x_fw_entry)
 
 /* RTL8211E and RTL8211F support up to three LEDs */
 #define RTL8211x_LED_COUNT			3
@@ -300,6 +339,11 @@ struct rtl821x_priv {
 	u16 iner;
 };
 
+struct rtl8261x_priv {
+	const char *fw_name;
+	bool fw_loaded;
+};
+
 static int rtl821x_read_page(struct phy_device *phydev)
 {
 	return __phy_read(phydev, RTL821x_PAGE_SELECT);
@@ -342,8 +386,16 @@ static int rtl821x_modify_ext_page(struct phy_device *phydev, u16 ext_page,
 
 static int rtl8261x_probe(struct phy_device *phydev)
 {
+	struct device *dev = &phydev->mdio.dev;
+	struct rtl8261x_priv *priv;
 	int sub_phy_id, ret;
 
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	phydev->priv = priv;
+
 	ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, RTL8261X_EXT_ADDR_REG,
 			    RTL_8261X_SUB_PHY_ID_ADDR);
 	if (ret < 0)
@@ -357,6 +409,7 @@ static int rtl8261x_probe(struct phy_device *phydev)
 
 	switch (sub_phy_id) {
 	case RTL8261C_CE_MODEL:
+		priv->fw_name = RTL8261C_CE_FW_NAME;
 		phydev_info(phydev, "RTL8261C detected (sub_id 0x%02x)\n", sub_phy_id);
 		break;
 
@@ -413,6 +466,152 @@ static int rtl8261x_read_status(struct phy_device *phydev)
 	return 0;
 }
 
+static int rtl8261x_verify_firmware(struct phy_device *phydev, const struct firmware *fw)
+{
+	const struct rtl8261x_fw_header *hdr;
+	u32 main_magic, sub_magic;
+	u32 calc_crc, file_crc;
+	size_t data_len;
+	u16 num_entries;
+
+	if (fw->size < FW_HEADER_SIZE) {
+		phydev_err(phydev, "Firmware too small: %zu bytes\n", fw->size);
+		return -EINVAL;
+	}
+
+	hdr = (const struct rtl8261x_fw_header *)fw->data;
+
+	main_magic = le32_to_cpu(hdr->main_magic);
+	if (main_magic != FW_MAIN_MAGIC) {
+		phydev_err(phydev, "Invalid firmware magic: 0x%08x\n", main_magic);
+		return -EINVAL;
+	}
+
+	sub_magic = le32_to_cpu(hdr->sub_magic);
+	if (sub_magic != FW_SUB_MAGIC_8261C) {
+		phydev_err(phydev, "Invalid sub magic: 0x%08x\n", sub_magic);
+		return -EINVAL;
+	}
+
+	num_entries = le16_to_cpu(hdr->num_entries);
+	data_len = num_entries * FW_ENTRY_SIZE;
+
+	if (fw->size != sizeof(*hdr) + data_len) {
+		phydev_err(phydev, "Firmware size mismatch\n");
+		return -EINVAL;
+	}
+
+	calc_crc = crc32(~0, fw->data + FW_HEADER_SIZE, data_len) ^ ~0;
+	file_crc = le32_to_cpu(hdr->crc32);
+
+	if (calc_crc != file_crc) {
+		phydev_err(phydev, "CRC32 mismatch: calculated=0x%08x file=0x%08x\n",
+			   calc_crc, file_crc);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int rtl8261x_fw_execute_entry(struct phy_device *phydev,
+				     const struct rtl8261x_fw_entry *entry)
+{
+	u16 addr, value, timeout_ms;
+	u8 dev, msb, lsb, poll_set;
+	u32 bits, expect_val;
+	int ret, val;
+
+	dev = entry->dev;
+	addr = le16_to_cpu(entry->addr);
+	msb = entry->msb;
+	lsb = entry->lsb;
+	value = le16_to_cpu(entry->value);
+	timeout_ms = le16_to_cpu(entry->timeout_ms);
+	poll_set = entry->poll_set;
+
+	if (timeout_ms == 0)
+		timeout_ms = RTL8261X_POLL_TIMEOUT_MS;
+
+	if (dev > RTL8261X_MAX_MMD_DEV) {
+		phydev_err(phydev, "invalid firmware MMD device: dev=%u\n", dev);
+		return -EINVAL;
+	}
+
+	if (msb > 15 || lsb > msb) {
+		phydev_err(phydev, "invalid firmware bits: msb=%u, lsb=%u\n", msb, lsb);
+		return -EINVAL;
+	}
+
+	switch (entry->type) {
+	case OP_WRITE:
+		ret = phy_modify_mmd(phydev, dev, addr,
+				     GENMASK(msb, lsb), (value << lsb) & GENMASK(msb, lsb));
+		if (ret)
+			return ret;
+		break;
+
+	case OP_POLL:
+		bits = GENMASK(msb, lsb);
+		expect_val = (value << lsb) & bits;
+
+		if (poll_set)
+			ret = phy_read_mmd_poll_timeout(phydev, dev, addr, val,
+							(val & bits) == expect_val,
+							1000, timeout_ms * 1000, false);
+		else
+			ret = phy_read_mmd_poll_timeout(phydev, dev, addr, val,
+							(val & bits) != expect_val,
+							1000, timeout_ms * 1000, false);
+		if (ret)
+			return ret;
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int rtl8261x_fw_load(struct phy_device *phydev)
+{
+	struct rtl8261x_priv *priv = phydev->priv;
+	const struct rtl8261x_fw_entry *entry;
+	const struct rtl8261x_fw_header *hdr;
+	const struct firmware *fw;
+	int ret, i;
+
+	if (!priv->fw_name)
+		return 0;
+
+	ret = request_firmware(&fw, priv->fw_name, &phydev->mdio.dev);
+	if (ret) {
+		phydev_err(phydev, "Failed to load firmware %s: %d\n", priv->fw_name, ret);
+		return ret;
+	}
+
+	ret = rtl8261x_verify_firmware(phydev, fw);
+	if (ret)
+		goto release_fw;
+
+	hdr = (const struct rtl8261x_fw_header *)fw->data;
+
+	entry = (const struct rtl8261x_fw_entry *)(fw->data + FW_HEADER_SIZE);
+	for (i = 0; i < le16_to_cpu(hdr->num_entries); i++, entry++) {
+		ret = rtl8261x_fw_execute_entry(phydev, entry);
+		if (ret) {
+			phydev_err(phydev, "Entry %d failed: %d\n", i, ret);
+			goto release_fw;
+		}
+	}
+
+	priv->fw_loaded = true;
+
+release_fw:
+	release_firmware(fw);
+	return ret;
+}
+
 static int rtl8261x_config_intr(struct phy_device *phydev)
 {
 	int ret;
@@ -490,6 +689,20 @@ static int rtl8261x_config_aneg(struct phy_device *phydev)
 	return 0;
 }
 
+static int rtl8261x_config_init(struct phy_device *phydev)
+{
+	struct rtl8261x_priv *priv = phydev->priv;
+
+	/* The firmware parameters are preserved across IEEE soft resets and
+	 * suspend/resume cycles. Reloading is only necessary after a power
+	 * cycle or hard reset.
+	 */
+	if (priv->fw_name && !priv->fw_loaded)
+		return rtl8261x_fw_load(phydev);
+
+	return 0;
+}
+
 static int rtl821x_probe(struct phy_device *phydev)
 {
 	struct device *dev = &phydev->mdio.dev;
@@ -3186,6 +3399,7 @@ static struct phy_driver realtek_drvs[] = {
 		PHY_ID_MATCH_EXACT(RTL_8261C_CG),
 		.name			= "Realtek RTL8261C 10Gbps PHY",
 		.probe			= rtl8261x_probe,
+		.config_init		= rtl8261x_config_init,
 		.get_features		= rtl8261x_get_features,
 		.config_aneg		= rtl8261x_config_aneg,
 		.read_status		= rtl8261x_read_status,
-- 
2.43.0


^ permalink raw reply related

* [PATCH net-next v4] sctp: socket: set *err = 0 on receive shutdown
From: luoqing @ 2026-07-21  2:25 UTC (permalink / raw)
  To: marcelo.leitner, lucien.xin, davem, edumazet, kuba, pabeni
  Cc: horms, linux-sctp, netdev, linux-kernel

From: Qing Luo <luoqing@kylinos.cn>

When sctp_skb_recv_datagram() detects RCV_SHUTDOWN, it breaks out
of the loop and returns NULL without setting *err. While current
callers happen to work correctly (sctp_recvmsg pre-initializes
err to 0, sctp_ulpevent_read_nxtinfo doesn't use err), this is
inconsistent with the generic __skb_wait_for_more_packets() in
net/core/datagram.c which explicitly sets *err = 0 on shutdown.

Set *err = 0 explicitly for correctness and robustness against
future callers.

Signed-off-by: Qing Luo <luoqing@kylinos.cn>
---
 net/sctp/socket.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/net/sctp/socket.c b/net/sctp/socket.c
index c7b9e325ec1c..b8ec295e3a2f 100644
--- a/net/sctp/socket.c
+++ b/net/sctp/socket.c
@@ -9117,9 +9117,10 @@ struct sk_buff *sctp_skb_recv_datagram(struct sock *sk, int flags, int *err)
 		if (error)
 			goto no_packet;
 
-		if (sk->sk_shutdown & RCV_SHUTDOWN)
+		if (sk->sk_shutdown & RCV_SHUTDOWN) {
+			*err = 0;
 			break;
-
+		}
 
 		/* User doesn't want to wait.  */
 		error = -EAGAIN;
-- 
2.25.1
Thanks for the review.

On reflection, I agree that the ERR_PTR refactoring should be dropped. Honestly, the refactored version ends up being more convoluted rather than simplifying things, so I’ll revert to the original &err interface to keep it consistent with skb_recv_datagram().

Regarding the *err = 0 fix for the shutdown path — my intention there was purely defensive, aligning with the pattern used in __skb_wait_for_more_packets(). As the commit message notes, the current callers are not actually affected by this, so no real bug is introduced. That said, if you feel this change is still unnecessary, I’m happy to drop it as well.


^ permalink raw reply related

* RE: [PATCH net v3 2/3] net: ethernet: oa_tc6: Improvement in buffer overflow handling
From: Selvamani Rajagopal @ 2026-07-21  2:27 UTC (permalink / raw)
  To: Andrew Lunn, parthiban.veerasooran@microchip.com,
	ciprian.regus@analog.com
  Cc: Simon Horman, andrew+netdev@lunn.ch, Piergiorgio Beruto,
	davem@davemloft.net, edumazet@google.com, kuba@kernel.org,
	pabeni@redhat.com, netdev@vger.kernel.org,
	linux-kernel@vger.kernel.org
In-Reply-To: <b86c8bff-6f78-42c6-8de9-ae280df5c18b@lunn.ch>

> Subject: Re: [PATCH net v3 2/3] net: ethernet: oa_tc6: Improvement in buffer overflow
> handling
> 
> You should probably repost it.

Thanks. I had follow-up one line change + comments. So, I submitted newer version.

> 
> The last couple of weeks things have been falling through the cracks
> due to vacation and conferences. Reviewer time has been limited.
> 
> It might also help if all the TC6 developers work together and review
> each others patches. Patchset having Reviewed-by: is more likely to
> get accepted without one of "big" reviewers looking at it.

Agree with you.

Parthiban, Ciprian,

Please add your review comments, if any, when you find some time as changes are in common code.
https://patchwork.kernel.org/project/netdevbpf/cover/20260720-fix-race-condition-and-crash-v4-0-8273e2f38a1f@onsemi.com/

> 
> Andrew


^ permalink raw reply

* [PATCH net] nfc: nci: free destination parameters when closing a connection
From: Linmao Li @ 2026-07-21  2:35 UTC (permalink / raw)
  To: David Heidelberg, Jakub Kicinski
  Cc: David S . Miller, Eric Dumazet, Paolo Abeni, Simon Horman,
	oe-linux-nfc, netdev, linux-kernel, Linmao Li

When a connection is closed, nci_core_conn_close_rsp_packet() frees
conn_info but not conn_info->dest_params, which is a separate devm
allocation. Each connect/close cycle leaks one dest_params until the
NFC device is removed. Free dest_params along with conn_info.

Fixes: 9b8d1a4cf2aa ("nfc: nci: Add an additional parameter to identify a connection id")
Signed-off-by: Linmao Li <lilinmao@kylinos.cn>
---
 net/nfc/nci/rsp.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/net/nfc/nci/rsp.c b/net/nfc/nci/rsp.c
index 6b2fa6bdbd14..21c2fe64490e 100644
--- a/net/nfc/nci/rsp.c
+++ b/net/nfc/nci/rsp.c
@@ -360,6 +360,7 @@ static void nci_core_conn_close_rsp_packet(struct nci_dev *ndev,
 			list_del(&conn_info->list);
 			if (conn_info == ndev->rf_conn_info)
 				ndev->rf_conn_info = NULL;
+			devm_kfree(&ndev->nfc_dev->dev, conn_info->dest_params);
 			devm_kfree(&ndev->nfc_dev->dev, conn_info);
 		}
 	}
-- 
2.25.1


^ permalink raw reply related

* [PATCH] rtase: fix double free of multi-frag skb on DMA map failure
From: Yun Lu @ 2026-07-21  2:38 UTC (permalink / raw)
  To: justinlai0215, larry.chiu, andrew+netdev, davem, edumazet, kuba,
	pabeni
  Cc: netdev

From: Yun Lu <luyun@kylinos.cn>

In rtase_start_xmit(), when the head buffer DMA mapping fails after
rtase_xmit_frags() has mapped all fragments, the error path clears
the fragment descriptors with rtase_tx_clear_range(), which frees
the skb through the last-frag slot and accounts tx_dropped. Control
then falls through to the common error label, which frees the same
skb a second time and counts it again.

Return right after clearing the fragments when the skb owns frags;
the no-frag case still drops through and frees the head skb once.

Fixes: d6e882b89fdf ("rtase: Implement .ndo_start_xmit function")
Signed-off-by: Yun Lu <luyun@kylinos.cn>
---
 drivers/net/ethernet/realtek/rtase/rtase_main.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/drivers/net/ethernet/realtek/rtase/rtase_main.c b/drivers/net/ethernet/realtek/rtase/rtase_main.c
index 255667775f0e..67f7fdada119 100644
--- a/drivers/net/ethernet/realtek/rtase/rtase_main.c
+++ b/drivers/net/ethernet/realtek/rtase/rtase_main.c
@@ -1426,6 +1426,9 @@ static netdev_tx_t rtase_start_xmit(struct sk_buff *skb,
 err_dma_1:
 	ring->skbuff[entry] = NULL;
 	rtase_tx_clear_range(ring, ring->cur_idx + 1, frags);
+	if (frags)
+		/* the frags were cleared above, along with the skb */
+		return NETDEV_TX_OK;
 
 err_dma_0:
 	tp->stats.tx_dropped++;
-- 
2.43.0


^ permalink raw reply related

* Re: [PATCH net v8 1/3] tipc: fix NULL deref in tipc_named_node_up() on empty publication list
From: Weiming Shi @ 2026-07-21  3:06 UTC (permalink / raw)
  To: Tung Quang Nguyen
  Cc: Xiang Mei, kernel test robot, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman,
	linux-kernel@vger.kernel.org, Jon Maloy, netdev@vger.kernel.org,
	tipc-discussion@lists.sourceforge.net
In-Reply-To: <GV1P189MB1988EC5A7F5CB5DD69F331AEC6C32@GV1P189MB1988.EURP189.PROD.OUTLOOK.COM>

Tung Quang Nguyen <tung.quang.nguyen@est.tech> 于2026年7月20日周一 23:41写道:
>
> >Subject: [PATCH net v8 1/3] tipc: fix NULL deref in tipc_named_node_up() on
> >empty publication list
> >
> >[The defer-to-workqueue approach is by Tung Nguyen. He posted it  on the
> >thread and asked us to test it, as the replacement for the  item-less bulk
> >approach. Since the RFC only exists as an inline  diff in the thread, it is folded
> >into this series so the fix is  self-contained.]
> >
> >named_distribute() stamps the last_bulk flag on the tail skb of the publication
> >list. When the list is empty no skb is enqueued and the tail access dereferences
> >NULL. tipc_named_node_up() hits this on an empty cluster_scope, which
> >happens with a node-id configuration where cluster_scope is populated only
> >later by tipc_net_finalize(). It is reachable by an unprivileged user over a UDP
> >bearer in a user+net namespace. The reported crash:
> >
> > KASAN: null-ptr-deref in range [0x00000000000000d8-0x00000000000000df]
> > RIP: 0010:tipc_named_node_up (net/tipc/name_distr.c:196)
> >  tipc_named_node_up (net/tipc/name_distr.c:196 net/tipc/name_distr.c:221)
> >  tipc_node_write_unlock (net/tipc/node.c:428)
> >  tipc_rcv (net/tipc/node.c:2185)
> >  tipc_udp_recv (net/tipc/udp_media.c:392)  Kernel panic - not syncing: Fatal
> >exception in interrupt
> >
> >When cluster_scope is empty at node-up, defer the bulk distribution to a
> >workqueue and wait for tipc_net_finalize() to publish the node-state name, so
> >named_distribute() always runs on a non-empty list. On allocation failure,
> >purge the partially built queue and bring the link down so the bulk distribution
> >restarts when the link comes up again.
> >
> >Fixes: cad2929dc432 ("tipc: update a binding service via broadcast")
> >Reported-by: Xiang Mei <xmei5@asu.edu>
> >Reported-by: kernel test robot <lkp@intel.com>
> >Closes: https://lore.kernel.org/oe-kbuild-all/202607180730.TwVgASDI-
> >lkp@intel.com/
> >Signed-off-by: Tung Nguyen <tung.quang.nguyen@est.tech>
> >Tested-by: Weiming Shi <bestswngs@gmail.com>
> >Signed-off-by: Weiming Shi <bestswngs@gmail.com>
> >---
> sashiko reports many critical/high issues: https://sashiko.dev/#/patchset/20260718092544.785289-1-bestswngs%40gmail.com
>
> I address all in below patch下面的补丁. Could you please test it ?
>
> ---
>  net/tipc/core.c       |  2 ++
>  net/tipc/core.h       |  4 +++
>  net/tipc/name_distr.c | 67 +++++++++++++++++++++++++++++++++++++------
>  net/tipc/name_distr.h |  3 +-
>  net/tipc/net.c        | 15 +++++++++-
>  net/tipc/node.c       | 61 +++++++++++++++++++++++++++++++++++++--
>  6 files changed, 139 insertions(+), 13 deletions(-)
>
> diff --git a/net/tipc/core.c b/net/tipc/core.c
> index 315975c3be81..52544b805dcc 100644
> --- a/net/tipc/core.c
> +++ b/net/tipc/core.c
> @@ -61,6 +61,8 @@ static int __net_init tipc_init_net(struct net *net)
>         tn->trial_addr = 0;
>         tn->addr_trial_end = 0;
>         tn->capabilities = TIPC_NODE_CAPABILITIES;
> +       atomic_set(&tn->finalized, 0);
> +       atomic_set(&tn-&tn->work_rescheduled, 0);
>         INIT_WORK(&tn-&tn->work, tipc_net_finalize_work);
>         memset(tn->node_id, 0, sizeof(tn->node_id));
>         memset(tn->node_id_string, 0, sizeof(tn->node_id_string));
> diff --git a/net/tipc/core.h b/net/tipc/core.h
> index 9ce5f9ff6cc0..52fdb9189cc3 100644
> --- a/net/tipc/core.h
> +++ b/net/tipc/core.h
> @@ -145,6 +145,10 @@ struct tipc_net {
>         struct work_struct work;
>         /* The numbers of work queues in schedule */
>         atomic_t wq_count;
> +       /* flag to indicate work has finished */
> +       atomic_t finalized;
> +       /* flag to reschedule work */
> +       atomic_t work_rescheduled;
>  };
>
>  static inline struct tipc_net *tipc_net(struct net *net)
> diff --git a/net/tipc/name_distr.c b/net/tipc/name_distr.c
> index ba4f4906e13b..3e32445cfbd0 100644
> --- a/net/tipc/name_distr.c
> +++ b/net/tipc/name_distr.c
> @@ -147,8 +147,8 @@ struct sk_buff *tipc_named_withdraw(struct net *net, struct publication *p)
>   * @pls: linked list of publication items to be packed into buffer chain
>   * @seqno: sequence number for this message
>   */
> -static void named_distribute(struct net *net, struct sk_buff_head *list,
> -                            u32 dnode, struct list_head *pls, u16 seqno)
> +static int named_distribute(struct net *net, struct sk_buff_head *list,
> +                           u32 dnode, struct list_head *pls, u16 seqno)
>  {
>         struct publication *publ;
>         struct sk_buff *skb = NULL;
> @@ -164,8 +164,9 @@ static void named_distribute(struct net *net, struct sk_buff_head *list,
>                         skb = named_prepare_buf(net, PUBLICATION, msg_rem,
>                                                 dnode);
>                         if (!skb) {
> +                               __skb_queue_purge(list);
>                                 pr_warn("Bulk publication failure\n");
> -                               return;
> +                               return 1;
>                         }
>                         hdr = buf_msg(skb);
>                         msg_set_bc_ack_invalid(hdr, true);
> @@ -195,15 +196,16 @@ static void named_distribute(struct net *net, struct sk_buff_head *list,
>         hdr = buf_msg(skb_peek_tail(list));
>         msg_set_last_bulk(hdr);
>         msg_set_named_seqno(hdr, seqno);
> +
> +       return 0;
>  }
>
>  /**
> - * tipc_named_node_up - tell specified node about all publications by this node
> + * tipc_named_distribute - distribute all publications to specified node
>   * @net: the associated network namespace
>   * @dnode: destination node
> - * @capabilities: peer node's capabilities
>   */
> -void tipc_named_node_up(struct net *net, u32 dnode, u16 capabilities)
> +static int tipc_named_distribute(struct net *net, u32 dnode)
>  {
>         struct name_table *nt = tipc_name_table(net);
>         struct tipc_net *tn = tipc_net(net);
> @@ -212,15 +214,62 @@ void tipc_named_node_up(struct net *net, u32 dnode, u16 capabilities)
>
>         __skb_queue_head_init(&head);
>         spin_lock_bh(&tn-&tn->nametbl_lock);
> -       if (!(capabilities & TIPC_NAMED_BCAST))
> -               nt->rc_dests++;
>         seqno = nt->snd_nxt;
>         spin_unlock_bh(&tn-&tn->nametbl_lock);
>
>         read_lock_bh(&nt->cluster_scope_lock);
> -       named_distribute(net, &head, dnode, &nt->cluster_scope, seqno);
> +       /* 1. tipc_net_finalize_work() is not scheduled because of namespace
> +        *    teardown.
> +        * 2. Or tipc_net_finalize() ---> tipc_nametbl_publish() has failed
> +        *    to insert node self address publication to nt->cluster_scope.
> +        * 3. Or tipc_net_finalize() ---> tipc_nametbl_publish() has not
> +        *    executed yet.
> +        */
> +       if (unlikely(list_empty(&nt->cluster_scope))) {
> +               read_unlock_bh(&nt->cluster_scope_lock);
> +               return 1;
> +       }
> +
> +       if (named_distribute(net, &head, dnode, &nt->cluster_scope, seqno)) {
> +               read_unlock_bh(&nt->cluster_scope_lock);
> +               return -ENOBUFS;
> +       }
>         tipc_node_xmit(net, &head, dnode, 0);
>         read_unlock_bh(&nt->cluster_scope_lock);
> +
> +       return 0;
> +}
> +
> +/**
> + * tipc_named_node_up - tell specified node about all publications by this node
> + * @net: the associated network namespace
> + * @dnode: destination node
> + * @capabilities: peer node's capabilities
> + */
> +int tipc_named_node_up(struct net *net, u32 dnode, u16 capabilities)
> +{
> +       struct name_table *nt = tipc_name_table(net);
> +       struct tipc_net *tn = tipc_net(net);
> +
> +       spin_lock_bh(&tn-&tn->nametbl_lock);
> +       if (!(capabilities & TIPC_NAMED_BCAST))
> +               nt->rc_dests++;
> +       spin_unlock_bh(&tn-&tn->nametbl_lock);
> +
> +       return tipc_named_distribute(net, dnode);
> +}
> +
> +/**
> + * tipc_named_dist_cluster_scope - distribute all publications to specified node
> + * @net: the associated network namespace
> + * @dnode: destination node
> + */
> +int tipc_named_dist_cluster_scope(struct net *net, u32 dnode)
> +{
> +       struct tipc_net *tn = tipc_net(net);
> +
> +       wait_var_event(&tn->finalized, atomic_read(&tn->finalized));
> +       return tipc_named_distribute(net, dnode);
>  }
>
>  /**
> diff --git a/net/tipc/name_distr.h b/net/tipc/name_distr.h
> index c677f6f082df..cadf4e8c3e66 100644
> --- a/net/tipc/name_distr.h
> +++ b/net/tipc/name_distr.h
> @@ -69,7 +69,8 @@ struct distr_item {
>
>  struct sk_buff *tipc_named_publish(struct net *net, struct publication *publ);
>  struct sk_buff *tipc_named_withdraw(struct net *net, struct publication *publ);
> -void tipc_named_node_up(struct net *net, u32 dnode, u16 capabilities);
> +int tipc_named_node_up(struct net *net, u32 dnode, u16 capabilities);
> +int tipc_named_dist_cluster_scope(struct net *net, u32 dnode);
>  void tipc_named_rcv(struct net *net, struct sk_buff_head *namedq,
>                     u16 *rcv_nxt, bool *open);
>  void tipc_named_reinit(struct net *net);
> diff --git a/net/tipc/net.c b/net/tipc/net.c
> index 7e65d0b0c4a8..78418515277e 100644
> --- a/net/tipc/net.c
> +++ b/net/tipc/net.c
> @@ -132,13 +132,26 @@ static void tipc_net_finalize(struct net *net, u32 addr)
>         tipc_uaddr(&ua, TIPC_SERVICE_RANGE, TIPC_CLUSTER_SCOPE,
>                    TIPC_NODE_STATE, addr, addr);
>
> +       if (atomic_read(&tn-&tn->work_rescheduled))
> +               goto publish;
> +
>         if (cmpxchg(&tn-&tn->node_addr, 0, addr))
>                 return;
>         tipc_set_node_addr(net, addr);
>         tipc_named_reinit(net);
>         tipc_sk_reinit(net);
>         tipc_mon_reinit_self(net);
> -       tipc_nametbl_publish(net, &ua, &sk, addr);
> +
> +publish:
> +       if (!tipc_nametbl_publish(net, &ua, &sk, addr)) {
> +               tn->trial_addr = addr;
> +               atomic_set(&tn-&tn->work_rescheduled, 1);
> +               schedule_work(&tn-&tn->work);
> +               return;
> +       }
> +       atomic_set(&tn-&tn->work_rescheduled, 0);
> +       atomic_set(&tn->finalized, 1);
> +       wake_up_var(&tn->finalized);
>  }
>
>  void tipc_net_finalize_work(struct work_struct *work)
> diff --git a/net/tipc/node.c b/net/tipc/node.c
> index 8e4ef2630ae4..afed72894722 100644
> --- a/net/tipc/node.c
> +++ b/net/tipc/node.c
> @@ -111,6 +111,8 @@ struct tipc_bclink_entry {
>   * @peer_net: peer's net namespace
>   * @peer_hash_mix: hash for this peer (FIXME)
>   * @crypto_rx: RX crypto handler
> + * @work: work item for bulk distribution of cluster scope publications
> + * @work_scheduled: flag to indicate the work has been scheduled
>   */
>  struct tipc_node {
>         u32 addr;
> @@ -145,6 +147,8 @@ struct tipc_node {
>  #ifdef CONFIG_TIPC_CRYPTO
>         struct tipc_crypto *crypto_rx;
>  #endif
> +       struct work_struct work;
> +       atomic_t work_scheduled;
>  };
>
>  /* Node FSM states and events:
> @@ -393,6 +397,27 @@ static void tipc_node_write_unlock_fast(struct tipc_node *n)
>         write_unlock_bh(&n->lock);
>  }
>
> +static void tipc_node_down(struct tipc_node *n)
> +{
> +       u32 bearer_id, bearer_cnt;
> +
> +       tipc_node_read_lock(n);
> +       bearer_cnt = n->link_cnt;
> +       tipc_node_read_unlock(n);
> +       for (bearer_id = 0; bearer_id < bearer_cnt; bearer_id++)
> +               tipc_node_link_down(n, bearer_id, false);
> +}
> +
> +static void tipc_node_dist_bulk(struct work_struct *work)
> +{
> +       struct tipc_node *node = container_of(work, struct tipc_node, work);
> +
> +       if (tipc_named_dist_cluster_scope(node->net, node->addr) < 0)
> +               tipc_node_down(node);
> +
> +       tipc_node_put(node);
> +}
> +
>  static void tipc_node_write_unlock(struct tipc_node *n)
>         __releases(n->lock)
>  {
> @@ -424,8 +449,23 @@ static void tipc_node_write_unlock(struct tipc_node *n)
>         if (flags & TIPC_NOTIFY_NODE_DOWN)
>                 tipc_publ_notify(net, publ_list, node, n->capabilities);
>
> -       if (flags & TIPC_NOTIFY_NODE_UP)
> -               tipc_named_node_up(net, node, n->capabilities);
> +       if (flags & TIPC_NOTIFY_NODE_UP) {
> +               int rc = 0;
> +
> +               rc = tipc_named_node_up(net, node, n->capabilities);
> +               /* Defer bulk distribution to work queue */
> +               if (rc > 0) {
> +                       atomic_set(&n->work_scheduled, 1);
> +                       tipc_node_get(n);
> +                       if (!schedule_work(&n->work))
> +                               tipc_node_put(n);
> +               } else if (rc < 0) {
> +                       /* Bring the node down to start over bulk distribution
> +                        * when the first link is up again.
> +                        */
> +                       tipc_node_down(n);
> +               }
> +       }
>
>         if (flags & TIPC_NOTIFY_LINK_UP) {
>                 tipc_mon_peer_up(net, node, bearer_id);
> @@ -564,6 +604,8 @@ struct tipc_node *tipc_node_create(struct net *net, u32 addr, u8 *peer_id,
>         INIT_LIST_HEAD(&n->list);
>         INIT_LIST_HEAD(&n->publ_list);
>         INIT_LIST_HEAD(&n->conn_sks);
> +       INIT_WORK(&n->work, tipc_node_dist_bulk);
> +       atomic_set(&n->work_scheduled, 0);
>         skb_queue_head_init(&n->bc_entry.namedq);
>         skb_queue_head_init(&n->bc_entry.inputq1);
>         __skb_queue_head_init(&n->bc_entry.arrvq);
> @@ -635,10 +677,25 @@ static void tipc_node_delete_from_list(struct tipc_node *node)
>
>  static void tipc_node_delete(struct tipc_node *node)
>  {
> +       struct tipc_net *tn = tipc_net(node->net);
> +
>         trace_tipc_node_delete(node, true, " ");
>         tipc_node_delete_from_list(node);
>
>         timer_delete_sync(&node->timer);
> +
> +       /* Wake up node work queue if tipc_net_finalize_work() is not
> +        * scheduled yet.
> +        */
> +       if (atomic_read(&node->work_scheduled)) {
> +               if (!atomic_read(&tn->finalized)) {
> +                       atomic_set(&tn->finalized, 1);
> +                       wake_up_var(&tn->finalized);
> +               }
> +
> +               cancel_work_sync(&node->work);
> +       }
> +
>         tipc_node_put(node);
>  }
>
> --
> 2.43.0




Hi Tung,

I tested this on v7.1-rc5 with the two-node setup (UDP bearers,
node-id addressing), as an unprivileged user and as root.
The original NULL deref is fixed, and the sashiko findings look addressed.

But I found one path the patch misses: tipc_node_cleanup(). It removes
a stale node with tipc_node_delete_from_list() directly, without the
wake/cancel
you added in tipc_node_delete(). If a deferred worker is still parked
when the stale timer fires, the node is removed from the list while
the worker is
sleeping in wait_var_event(). Since the node is off the list, the wake
in tipc_node_delete() can no longer reach it. The worker stays in D
state and
keeps its node reference, so the node struct leaks as well.

This state is reachable from userspace without any kernel change:
filling local_publ_count to TIPC_MAX_PUBL with node-scope binds makes
tipc_net_finalize()'s publish fail, and the work_rescheduled retry
then keeps finalized at 0, so the link-up defers the worker for as
long as the table
stays full. (The retry also也重试 busy-loops, printing "tipc: Bind
failed, max limit 65535 reached" each round.) Bringing the link down
makes the peer node go
stale NODE_CLEANUP_AFTER (300s) later, with the worker still parked.
The hung task watchdog flags it:

```
     [  370.440401] INFO: task kworker/0:0:9 blocked for more than 245 seconds.
     [  370.441580] Workqueue: events tipc_node_dist_bulk
     [  370.442803] Call Trace:
     [  370.443151]  __schedule+0x18bf/0x4680
     [  370.444293]  tipc_named_dist_cluster_scope+0x1a0/0x220
     [  370.446076]  tipc_node_dist_bulk+0x6d/0x1b0
     [  370.446218]  process_one_work+0x845/0x1a60
```

With a test-only marker printk in tipc_node_cleanup() I can see the
stale timer remove both nodes at ~368s and ~375s, i.e. while the
workers above are
still parked. Nothing wakes them on that path; in this run they only
exit because killing the reproducer closes the bind sockets, so the
next retry's
publish succeeds. With a persistent failure source (e.g. memory
pressure) the worker would stay parked across the netns teardown as
well.


```
[  247.560544] INFO: task kworker/0:0:9 blocked for more than 122 seconds.
     [  247.568089] INFO: task kworker/0:2:67 blocked for more than 122 seconds.

     [  370.440401] INFO: task kworker/0:0:9 blocked for more than 245 seconds.
     [  370.441580] Workqueue: events tipc_node_dist_bulk
     [  370.442803] Call Trace:
     [  370.443151]  __schedule+0x18bf/0x4680
     [  370.444293]  tipc_named_dist_cluster_scope+0x1a0/0x220     ←
wait_var_event 睡眠点
     [  370.446076]  tipc_node_dist_bulk+0x6d/0x1b0
     [  370.446218]  process_one_work+0x845/0x1a60

```


The Configs and reproducer :
```
CONFIG_TIPC=y,
CONFIG_TIPC_MEDIA_UDP=y, CONFIG_USER_NS=y, CONFIG_NET_NS=y,
CONFIG_KASAN=y (crash on the unpatched kernel),
CONFIG_DETECT_HUNG_TASK=y
```

```c
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sched.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/genetlink.h>
#include <linux/if_link.h>
#include <linux/veth.h>
#include <linux/tipc.h>

#ifndef AF_TIPC
#define AF_TIPC 30
#endif
#ifndef SOCK_RDM
#define SOCK_RDM 4
#endif

#ifndef VETH_INFO_PEER
#define VETH_INFO_PEER 1
#endif

#define TIPC_GENL_V2_NAME "TIPCv2"

enum {
        TIPC_NL_UNSPEC,
        TIPC_NL_LEGACY,
        TIPC_NL_BEARER_DISABLE,
        TIPC_NL_BEARER_ENABLE,
        TIPC_NL_BEARER_GET,
        TIPC_NL_BEARER_SET,
        TIPC_NL_SOCK_GET,
        TIPC_NL_PUBL_GET,
        TIPC_NL_LINK_GET,
        TIPC_NL_LINK_SET,
        TIPC_NL_LINK_RESET_STATS,
        TIPC_NL_MEDIA_GET,
        TIPC_NL_MEDIA_SET,
        TIPC_NL_NODE_GET,
        TIPC_NL_NET_GET,
        TIPC_NL_NET_SET,
};

enum {
        TIPC_NLA_UNSPEC,
        TIPC_NLA_BEARER,
        TIPC_NLA_SOCK,
        TIPC_NLA_PUBL,
        TIPC_NLA_LINK,
        TIPC_NLA_MEDIA,
        TIPC_NLA_NODE,
        TIPC_NLA_NET,
};

enum {
        TIPC_NLA_BEARER_UNSPEC,
        TIPC_NLA_BEARER_NAME,
        TIPC_NLA_BEARER_PROP,
        TIPC_NLA_BEARER_DOMAIN,
        TIPC_NLA_BEARER_UDP_OPTS,
};

enum {
        TIPC_NLA_UDP_UNSPEC,
        TIPC_NLA_UDP_LOCAL,
        TIPC_NLA_UDP_REMOTE,
        TIPC_NLA_UDP_MULTI_REMOTEIP,
};

enum {
        TIPC_NLA_NET_UNSPEC,
        TIPC_NLA_NET_ID,
        TIPC_NLA_NET_ADDR,
        TIPC_NLA_NET_NODEID,
        TIPC_NLA_NET_NODEID_W1,
};

#define TIPC_UDP_PORT 6118
#define NLA_ALIGNTO 4
#define MY_NLA_ALIGN(len) (((len) + NLA_ALIGNTO - 1) & ~(NLA_ALIGNTO - 1))

static int tipc_family;

static int nl_open(int protocol)
{
        int fd = socket(AF_NETLINK, SOCK_RAW, protocol);
        if (fd < 0) { perror("socket(NETLINK)"); exit(1); }
        struct sockaddr_nl sa = { .nl_family = AF_NETLINK };
        if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
                perror("bind(NETLINK)"); exit(1);
        }
        return fd;
}

static void *nla_put(char *buf, int *off, int type, const void *data, int len)
{
        struct nlattr *a = (struct nlattr *)(buf + *off);
        a->nla_type = type;
        a->nla_len = NLA_HDRLEN + len;
        if (len)
                memcpy((char *)a + NLA_HDRLEN, data, len);
        *off += MY_NLA_ALIGN(a->nla_len);
        return a;
}

static struct nlattr *nla_nest_start(char *buf, int *off, int type)
{
        struct nlattr *a = (struct nlattr *)(buf + *off);
        a->nla_type = type | NLA_F_NESTED;
        *off += NLA_HDRLEN;
        return a;
}

static void nla_nest_end(char *buf, int *off, struct nlattr *start)
{
        start->nla_len = (buf + *off) - (char *)start;
}

static int genl_resolve_family(int fd, const char *name)
{
        char buf[1024];
        memset(buf, 0, sizeof(buf));
        struct nlmsghdr *nlh = (struct nlmsghdr *)buf;
        struct genlmsghdrgenlmsghdr *gh = (struct genlmsghdrgenlmsghdr
*)NLMSG_DATA(nlh);
        int off = NLMSG_HDRLEN + GENL_HDRLEN;

        nlh->nlmsg_type = GENL_ID_CTRL;
        nlh->nlmsg_flags = NLM_F_REQUEST;
        nlh->nlmsg_seq = 1;
        gh->cmd = CTRL_CMD_GETFAMILY;
        gh->version = 1;
        nla_put(buf, &off, CTRL_ATTR_FAMILY_NAME, name, strlen(name) + 1);
        nlh->nlmsg_len = off;

        if (send(fd, buf, nlh->nlmsg_len, 0) < 0) { perror("send genl
resolve"); exit(1); }

        char rbuf[4096];
        int n = recv(fd, rbuf, sizeof(rbuf), 0);
        if (n < 0) { perror("recv genl resolve"); exit(1); }

        struct nlmsghdr *rh = (struct nlmsghdr *)rbuf;
        if (rh->nlmsg_type == NLMSG_ERROR)
                return -1;
        struct nlattr *a = (struct nlattr *)((char *)NLMSG_DATA(rh) +
GENL_HDRLEN);
        int alen阿伦 = rh->nlmsg_len - NLMSG_HDRLEN - GENL_HDRLEN;
        while (alen > 0) {
                if (a->nla_type == CTRL_ATTR_FAMILY_ID)
                        return *(uint16_t *)((char *)a + NLA_HDRLEN);
                int step = MY_NLA_ALIGN(a->nla_len);
                alen -= step;
                a = (struct nlattr *)((char *)a + step);
        }
        return -1;
}

static int nl_recv_ack(int fd, const char *what)
{
        char rbuf[8192];
        int n = recv(fd, rbuf, sizeof(rbuf), 0);
        if (n < 0) { fprintf(stderr, "recv ack (%s): %s\n", what,
strerror(errno)); return -1; }
        struct nlmsghdr *rh = (struct nlmsghdr *)rbuf;
        if (rh->nlmsg_type == NLMSG_ERROR) {
                struct nlmsgerr *e = NLMSG_DATA(rh);
                if (e->error != 0)
                        fprintf(stderr, "[%s] netlink error: %d
(%s)\n", what, e->error, strerror(-e->error));
                return e->error;
        }
        return 0;
}

static void *rta_put(char *buf, int *off, int type, const void *data, int len)
{
        struct rtattr *a = (struct rtattr *)(buf + *off);
        a->rta_type = type;
        a->rta_len = RTA_LENGTH(len);
        if (len) memcpy(RTA_DATA(a), data, len);
        *off += RTA_ALIGN(a->rta_len);
        return a;
}

static struct rtattr *rta_nest(char *buf, int *off, int type)
{
        struct rtattr *a = (struct rtattr *)(buf + *off);
        a->rta_type = type | NLA_F_NESTED;
        *off += RTA_LENGTH(0);
        return a;
}

static void rta_nest_end(char *buf, int *off, struct rtattr *start)
{
        start->rta_len = (buf + *off) - (char *)start;
}

static void create_veth(int fd, const char *n0, const char *n1, int peer_nsfd)
{
        char buf[2048];
        memset(buf, 0, sizeof(buf));
        struct nlmsghdr *nlh = (struct nlmsghdr *)buf;
        struct ifinfomsg *ifi = NLMSG_DATA(nlh);
        int off = NLMSG_HDRLEN + sizeof(*ifi);

        nlh->nlmsg_type = RTM_NEWLINK;
        nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE |
NLM_F_EXCL;
        nlh->nlmsg_seq = 10;
        ifi->ifi_family = AF_UNSPEC;

        rta_put(buf, &off, IFLA_IFNAME, n0, strlen(n0) + 1);
        struct rtattr *linfo = rta_nest(buf, &off, IFLA_LINKINFO);
        rta_put(buf, &off, IFLA_INFO_KIND, "veth", 5);
        struct rtattr *idata = rta_nest(buf, &off, IFLA_INFO_DATA);
        struct rtattr *peer = rta_nest(buf, &off, VETH_INFO_PEER);
        struct ifinfomsg *pifi = (struct ifinfomsg *)(buf + off);
        memset(pifi, 0, sizeof(*pifi));
        off += sizeof(*pifi);
        rta_put(buf, &off, IFLA_IFNAME, n1, strlen(n1) + 1);
        rta_put(buf, &off, IFLA_NET_NS_FD, &peer_nsfd, sizeof(peer_nsfd));
        rta_nest_end(buf, &off, peer);
        rta_nest_end(buf, &off, idata);
        rta_nest_end(buf, &off, linfo);
        nlh->nlmsg_len = off;

        if (send(fd, buf, nlh->nlmsg_len, 0) < 0) { perror("send
veth"); exit(1); }
        int e = nl_recv_ack(fd, "create_veth");
        if (e && e != -EEXIST) { fprintf(stderr, "veth create failed:
%d\n", e); exit(1); }
}

static void set_if_up(int fd, const char *name)
{
        char buf[512];
        memset(buf, 0, sizeof(buf));
        struct nlmsghdr *nlh = (struct nlmsghdr *)buf;
        struct ifinfomsg *ifi = NLMSG_DATA(nlh);
        int off = NLMSG_HDRLEN + sizeof(*ifi);
        nlh->nlmsg_type = RTM_NEWLINK;
        nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
        nlh->nlmsg_seq = 20;
        ifi->ifi_family = AF_UNSPEC;
        ifi->ifi_index = if_nametoindex(name);
        ifi->ifi_flags = IFF_UP;
        ifi->ifi_change = IFF_UP;
        nlh->nlmsg_len = off;
        if (send(fd, buf, nlh->nlmsg_len, 0) < 0) { perror("send up");
exit(1); }
        nl_recv_ack(fd, "set_if_up");
}

static void add_ipv4(int fd, const char *name, const char *ip, int prefix)
{
        char buf[512];
        memset(buf, 0, sizeof(buf));
        struct nlmsghdr *nlh = (struct nlmsghdr *)buf;
        struct ifaddrmsg *ifa = NLMSG_DATA(nlh);
        int off = NLMSG_HDRLEN + sizeof(*ifa);
        nlh->nlmsg_type = RTM_NEWADDR;
        nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE |
NLM_F_REPLACE;
        nlh->nlmsg_seq = 30;
        ifa->ifa_family = AF_INET;
        ifa->ifa_prefixlen = prefix;
        ifa->ifa_scope = 0;
        ifa->ifa_index = if_nametoindex(name);

        struct in_addr a;
        inet_pton(AF_INET, ip, &a);
        rta_put(buf, &off, IFA_LOCAL, &a, 4);
        rta_put(buf, &off, IFA_ADDRESS, &a, 4);
        nlh->nlmsg_len = off;
        if (send(fd, buf, nlh->nlmsg_len, 0) < 0) { perror("send
addr"); exit(1); }
        nl_recv_ack(fd, "add_ipv4");
}

static void tipc_set_node_id(int fd, uint64_t w0, uint64_t w1)
{
        char buf[1024];
        memset(buf, 0, sizeof(buf));
        struct nlmsghdr *nlh = (struct nlmsghdr *)buf;
        struct genlmsghdrgenlmsghdr *gh = (struct genlmsghdrgenlmsghdr
*)NLMSG_DATA(nlh);
        int off = NLMSG_HDRLEN + GENL_HDRLEN;
        nlh->nlmsg_type = tipc_family;
        nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
        nlh->nlmsg_seq = 40;
        gh->cmd = TIPC_NL_NET_SET;
        gh->version = 1;

        struct nlattr *net = nla_nest_start(buf, &off, TIPC_NLA_NET);
        nla_put(buf, &off, TIPC_NLA_NET_NODEID, &w0, sizeof(w0));
        nla_put(buf, &off, TIPC_NLA_NET_NODEID_W1, &w1, sizeof(w1));
        nla_nest_end(buf, &off, net);
        nlh->nlmsg_len = off;

        if (send(fd, buf, nlh->nlmsg_len, 0) < 0) { perror("send
net_set"); exit(1); }
        int e = nl_recv_ack(fd, "tipc_set_node_id");
        if (e) fprintf(stderr, "net_set(nodeid) returned %d\n", e);
}

static void put_sockaddr_storage_v4(char *buf, int *off, int type,
const char *ip, int port)
{
        struct sockaddr_storage ss;
        memset(&ss, 0, sizeof(ss));
        struct sockaddr_in *sin = (struct sockaddr_in *)&ss;
        sin->sin_family = AF_INET;
        sin->sin_port = htons(port);
        inet_pton(AF_INET, ip, &sin->sin_addr);
        nla_put(buf, off, type, &ss, sizeof(ss));
}

static void tipc_udp_bearer(int fd, int cmd, const char *bname,
                            const char *local_ip, const char *remote_ip)
{
        char buf[2048];
        memset(buf, 0, sizeof(buf));
        struct nlmsghdr *nlh = (struct nlmsghdr *)buf;
        struct genlmsghdrgenlmsghdr *gh = (struct genlmsghdrgenlmsghdr
*)NLMSG_DATA(nlh);
        int off = NLMSG_HDRLEN + GENL_HDRLEN;
        nlh->nlmsg_type = tipc_family;
        nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
        nlh->nlmsg_seq = 50;
        gh->cmd = cmd;
        gh->version = 1;

        struct nlattr *bearer = nla_nest_start(buf, &off, TIPC_NLA_BEARER);
        nla_put(buf, &off, TIPC_NLA_BEARER_NAME, bname名称, strlen(bname名称) + 1);
        if (cmd == TIPC_NL_BEARER_ENABLE) {
                struct nlattr *udp = nla_nest_start(buf, &off,
TIPC_NLA_BEARER_UDP_OPTS);
                put_sockaddr_storage_v4(buf, &off, TIPC_NLA_UDP_LOCAL,
local_ip, TIPC_UDP_PORT);
                put_sockaddr_storage_v4(buf, &off,
TIPC_NLA_UDP_REMOTE, remote_ip, TIPC_UDP_PORT);
                nla_nest_end(buf, &off, udp);
        }
        nla_nest_end(buf, &off, bearer);
        nlh->nlmsg_len = off;

        if (send(fd, buf, nlh->nlmsg_len, 0) < 0) { perror("send
bearer"); exit(1); }
        int e = nl_recv_ack(fd, "tipc_udp_bearer");
        if (e) fprintf(stderr, "bearer cmd %d (%s) returned %d\n",
cmd, bname名称, e);
}

static int write_file(const char *path, const char *val)
{
        int fd = open(path, O_WRONLY);
        if (fd < 0) return -1;
        int r = write(fd, val, strlen(val));
        close(fd);
        return r;
}

static int bindfill_node_scope(int target)
{
        int fd = socket(AF_TIPC, SOCK_RDM, 0);
        if (fd < 0) { perror("socket(AF_TIPC)"); exit(1); }
        int ok = 0, fail = 0;
        for (int i = 0; i < target + 1000; i++) {
                struct sockaddr_tipc sa;
                memset(&sa, 0, sizeof(sa));
                sa.family = AF_TIPC;
                sa.addrtype = TIPC_SERVICE_ADDR;
                sa.scope = TIPC_NODE_SCOPE;
                sa.addr.nameseq.type = 100000 + i;
                sa.addr.nameseq.lower = 100000 + i;
                sa.addr.nameseq.upper = 100000 + i;
                if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) == 0) {
                        ok++;
                        fail = 0;
                } else if (++fail > 100) {
                        break;
                }
        }
        fprintf(stderr, "[*] bound %d node-scope publications (cap
reached)\n", ok);
        if (ok < 65000) { fprintf(stderr, "bindfill shortfall\n"); exit(1); }
        return fd;
}

static void child_main(int sync_rd, int sync_wr)
{
        char c;
        write(sync_wr, "R", 1);
        if (read(sync_rd, &c, 1) != 1) _exit(1);

        int rfd = nl_open(NETLINK_ROUTE);
        set_if_up(rfd, "lo");
        set_if_up(rfd, "veth1");
        add_ipv4(rfd, "veth1", "10.0.0.2", 24);

        int gfd = nl_open(NETLINK_GENERIC);
        tipc_family = genl_resolve_family(gfd, TIPC_GENL_V2_NAME);
        if (tipc_family < 0) _exit(1);

        int bfd = bindfill_node_scope(65535);
        tipc_set_node_id(gfd, 0x2222222222222222ULL, 0x2222222222222222ULL);
        tipc_udp_bearer(gfd, TIPC_NL_BEARER_ENABLE, "udp:b1",
"10.0.0.2", "10.0.0.1");
        write(sync_wr, "C", 1);

        sleep(35);
        tipc_udp_bearer(gfd, TIPC_NL_BEARER_DISABLE, "udp:b1", NULL, NULL);
        fprintf(stderr, "[child] b1 disabled, keeping it down\n");
        (void)bfd;
        for (;;) pause();
}

int main(void)
{
        setvbuf(stdout, NULL, _IONBF, 0);
        setvbuf(stderr, NULL, _IONBF, 0);

        uid_t uid = getuid();
        gid_t gid = getgid();

        if (unshare(CLONE_NEWUSER | CLONE_NEWNET) == 0) {
                write_file("/proc/self/setgroups", "deny");
                char m[64];
                snprintf(m, sizeof(m), "0 %d 1", uid);
                write_file("/proc/self/uid_map", m);
                snprintf(m, sizeof(m), "0 %d 1", gid);
                write_file("/proc/self/gid_map", m);
        } else if (unshare(CLONE_NEWNET) < 0) {
                perror("unshare");
                return 1;
        }

        int p2c[2], c2p[2];
        if (pipe(p2c) < 0 || pipe(c2p) < 0) { perror("pipe"); return 1; }

        pid_t pid = fork();
        if (pid == 0) {
                close(p2c[1]);
                close(c2p[0]);
                if (unshare(CLONE_NEWNET) < 0) _exit(1);
                child_main(p2c[0], c2p[1]);
                _exit(0);
        }
        close(p2c[0]);
        close(c2p[1]);

        char c;
        if (read(c2p[0], &c, 1) != 1) { fprintf(stderr, "child not
ready\n"); return 1; }

        char nspath[64];
        snprintf(nspath, sizeof(nspath), "/proc/%d/ns/net", pid);
        int nsfd = open(nspath, O_RDONLY);
        if (nsfd < 0) { perror("open ns"); return 1; }

        int rfd = nl_open(NETLINK_ROUTE);
        create_veth(rfd, "veth0", "veth1", nsfd);
        close(nsfd);
        set_if_up(rfd, "lo");
        set_if_up(rfd, "veth0");
        add_ipv4(rfd, "veth0", "10.0.0.1", 24);
        write(p2c[1], "G", 1);

        int gfd = nl_open(NETLINK_GENERIC);
        tipc_family = genl_resolve_family(gfd, TIPC_GENL_V2_NAME);
        if (tipc_family < 0) return 1;
        int bfd = bindfill_node_scope(65535);
        tipc_set_node_id(gfd, 0x1111111111111111ULL, 0x1111111111111111ULL);

        if (read(c2p[0], &c, 1) != 1)
                fprintf(stderr, "child config sync missed\n");
        tipc_udp_bearer(gfd, TIPC_NL_BEARER_ENABLE, "udp:b0",
"10.0.0.1", "10.0.0.2");

        fprintf(stderr, "[*] nodes configured; waiting for stale cleanup\n");
        (void)bfd;
        sleep(400);
        kill(pid, SIGKILL);
        waitpid(pid, NULL, 0);
        fprintf(stderr, "[*] done\n");
        return 0;
}

```


Do you think tipc_node_cleanup() should go through the same wake logic
as tipc_node_delete()? Happy to test the next version.

Thanks,
Weiming

^ permalink raw reply

* RE: [PATCH net v8 1/3] tipc: fix NULL deref in tipc_named_node_up() on empty publication list
From: Tung Quang Nguyen @ 2026-07-21  3:24 UTC (permalink / raw)
  To: Weiming Shi
  Cc: Xiang Mei, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Simon Horman, linux-kernel@vger.kernel.org,
	Jon Maloy, netdev@vger.kernel.org,
	tipc-discussion@lists.sourceforge.net
In-Reply-To: <CANgPUi2hfRO8OebwBFm2f8240DYmqdPYNQtWyQL0VOP8beeSFQ@mail.gmail.com>

>Subject: Re: [PATCH net v8 1/3] tipc: fix NULL deref in tipc_named_node_up()
>on empty publication list
>
>Hi Tung,
>
>I tested this on v7.1-rc5 with the two-node setup (UDP bearers, node-id
>addressing), as an unprivileged user and as root.
>The original NULL deref is fixed, and the sashiko findings look addressed.
>
>But I found one path the patch misses: tipc_node_cleanup(). It removes a stale
>node with tipc_node_delete_from_list() directly, without the wake/cancel you
>added in tipc_node_delete(). If a deferred worker is still parked when the stale
>timer fires, the node is removed from the list while the worker is sleeping in
>wait_var_event(). Since the node is off the list, the wake in tipc_node_delete()
>can no longer reach it. The worker stays in D state and keeps its node
>reference, so the node struct leaks as well.
>
>This state is reachable from userspace without any kernel change:
>filling local_publ_count to TIPC_MAX_PUBL with node-scope binds makes
>tipc_net_finalize()'s publish fail, and the work_rescheduled retry then keeps
>finalized at 0, so the link-up defers the worker for as long as the table stays full.
>(The retry also也重试 busy-loops, printing "tipc: Bind failed, max limit 65535
>reached" each round.) Bringing the link down makes the peer node go stale
>NODE_CLEANUP_AFTER (300s) later, with the worker still parked.
>The hung task watchdog flags it:
>
>```
>     [  370.440401] INFO: task kworker/0:0:9 blocked for more than 245 seconds.
>     [  370.441580] Workqueue: events tipc_node_dist_bulk
>     [  370.442803] Call Trace:
>     [  370.443151]  __schedule+0x18bf/0x4680
>     [  370.444293]  tipc_named_dist_cluster_scope+0x1a0/0x220
>     [  370.446076]  tipc_node_dist_bulk+0x6d/0x1b0
>     [  370.446218]  process_one_work+0x845/0x1a60 ```
>
>With a test-only marker printk in tipc_node_cleanup() I can see the stale timer
>remove both nodes at ~368s and ~375s, i.e. while the workers above are still
>parked. Nothing wakes them on that path; in this run they only exit because
>killing the reproducer closes the bind sockets, so the next retry's publish
>succeeds. With a persistent failure source (e.g. memory
>pressure) the worker would stay parked across the netns teardown as well.
>
>
>```
>[  247.560544] INFO: task kworker/0:0:9 blocked for more than 122 seconds.
>     [  247.568089] INFO: task kworker/0:2:67 blocked for more than 122
>seconds.
>
>     [  370.440401] INFO: task kworker/0:0:9 blocked for more than 245 seconds.
>     [  370.441580] Workqueue: events tipc_node_dist_bulk
>     [  370.442803] Call Trace:
>     [  370.443151]  __schedule+0x18bf/0x4680
>     [  370.444293]  tipc_named_dist_cluster_scope+0x1a0/0x220     ←
>wait_var_event 睡眠点
>     [  370.446076]  tipc_node_dist_bulk+0x6d/0x1b0
>     [  370.446218]  process_one_work+0x845/0x1a60
>
>```
>
>
>The Configs and reproducer :
>```
>CONFIG_TIPC=y,
>CONFIG_TIPC_MEDIA_UDP=y, CONFIG_USER_NS=y, CONFIG_NET_NS=y,
>CONFIG_KASAN=y (crash on the unpatched kernel),
>CONFIG_DETECT_HUNG_TASK=y ```
>
>```c
>#define _GNU_SOURCE
>#include <stdio.h>
>#include <stdlib.h>
>#include <string.h>
>#include <unistd.h>
>#include <errno.h>
>#include <sched.h>
>#include <fcntl.h>
>#include <signal.h>
>#include <sys/types.h>
>#include <sys/wait.h>
>#include <sys/socket.h>
>#include <arpa/inet.h>
>#include <net/if.h>
>#include <linux/netlink.h>
>#include <linux/rtnetlink.h>
>#include <linux/genetlink.h>
>#include <linux/if_link.h>
>#include <linux/veth.h>
>#include <linux/tipc.h>
>
>#ifndef AF_TIPC
>#define AF_TIPC 30
>#endif
>#ifndef SOCK_RDM
>#define SOCK_RDM 4
>#endif
>
>#ifndef VETH_INFO_PEER
>#define VETH_INFO_PEER 1
>#endif
>
>#define TIPC_GENL_V2_NAME "TIPCv2"
>
>enum {
>        TIPC_NL_UNSPEC,
>        TIPC_NL_LEGACY,
>        TIPC_NL_BEARER_DISABLE,
>        TIPC_NL_BEARER_ENABLE,
>        TIPC_NL_BEARER_GET,
>        TIPC_NL_BEARER_SET,
>        TIPC_NL_SOCK_GET,
>        TIPC_NL_PUBL_GET,
>        TIPC_NL_LINK_GET,
>        TIPC_NL_LINK_SET,
>        TIPC_NL_LINK_RESET_STATS,
>        TIPC_NL_MEDIA_GET,
>        TIPC_NL_MEDIA_SET,
>        TIPC_NL_NODE_GET,
>        TIPC_NL_NET_GET,
>        TIPC_NL_NET_SET,
>};
>
>enum {
>        TIPC_NLA_UNSPEC,
>        TIPC_NLA_BEARER,
>        TIPC_NLA_SOCK,
>        TIPC_NLA_PUBL,
>        TIPC_NLA_LINK,
>        TIPC_NLA_MEDIA,
>        TIPC_NLA_NODE,
>        TIPC_NLA_NET,
>};
>
>enum {
>        TIPC_NLA_BEARER_UNSPEC,
>        TIPC_NLA_BEARER_NAME,
>        TIPC_NLA_BEARER_PROP,
>        TIPC_NLA_BEARER_DOMAIN,
>        TIPC_NLA_BEARER_UDP_OPTS,
>};
>
>enum {
>        TIPC_NLA_UDP_UNSPEC,
>        TIPC_NLA_UDP_LOCAL,
>        TIPC_NLA_UDP_REMOTE,
>        TIPC_NLA_UDP_MULTI_REMOTEIP,
>};
>
>enum {
>        TIPC_NLA_NET_UNSPEC,
>        TIPC_NLA_NET_ID,
>        TIPC_NLA_NET_ADDR,
>        TIPC_NLA_NET_NODEID,
>        TIPC_NLA_NET_NODEID_W1,
>};
>
>#define TIPC_UDP_PORT 6118
>#define NLA_ALIGNTO 4
>#define MY_NLA_ALIGN(len) (((len) + NLA_ALIGNTO - 1) & ~(NLA_ALIGNTO -
>1))
>
>static int tipc_family;
>
>static int nl_open(int protocol)
>{
>        int fd = socket(AF_NETLINK, SOCK_RAW, protocol);
>        if (fd < 0) { perror("socket(NETLINK)"); exit(1); }
>        struct sockaddr_nl sa = { .nl_family = AF_NETLINK };
>        if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
>                perror("bind(NETLINK)"); exit(1);
>        }
>        return fd;
>}
>
>static void *nla_put(char *buf, int *off, int type, const void *data, int len) {
>        struct nlattr *a = (struct nlattr *)(buf + *off);
>        a->nla_type = type;
>        a->nla_len = NLA_HDRLEN + len;
>        if (len)
>                memcpy((char *)a + NLA_HDRLEN, data, len);
>        *off += MY_NLA_ALIGN(a->nla_len);
>        return a;
>}
>
>static struct nlattr *nla_nest_start(char *buf, int *off, int type) {
>        struct nlattr *a = (struct nlattr *)(buf + *off);
>        a->nla_type = type | NLA_F_NESTED;
>        *off += NLA_HDRLEN;
>        return a;
>}
>
>static void nla_nest_end(char *buf, int *off, struct nlattr *start) {
>        start->nla_len = (buf + *off) - (char *)start; }
>
>static int genl_resolve_family(int fd, const char *name) {
>        char buf[1024];
>        memset(buf, 0, sizeof(buf));
>        struct nlmsghdr *nlh = (struct nlmsghdr *)buf;
>        struct genlmsghdrgenlmsghdr *gh = (struct genlmsghdrgenlmsghdr
>*)NLMSG_DATA(nlh);
>        int off = NLMSG_HDRLEN + GENL_HDRLEN;
>
>        nlh->nlmsg_type = GENL_ID_CTRL;
>        nlh->nlmsg_flags = NLM_F_REQUEST;
>        nlh->nlmsg_seq = 1;
>        gh->cmd = CTRL_CMD_GETFAMILY;
>        gh->version = 1;
>        nla_put(buf, &off, CTRL_ATTR_FAMILY_NAME, name, strlen(name) + 1);
>        nlh->nlmsg_len = off;
>
>        if (send(fd, buf, nlh->nlmsg_len, 0) < 0) { perror("send genl resolve");
>exit(1); }
>
>        char rbuf[4096];
>        int n = recv(fd, rbuf, sizeof(rbuf), 0);
>        if (n < 0) { perror("recv genl resolve"); exit(1); }
>
>        struct nlmsghdr *rh = (struct nlmsghdr *)rbuf;
>        if (rh->nlmsg_type == NLMSG_ERROR)
>                return -1;
>        struct nlattr *a = (struct nlattr *)((char *)NLMSG_DATA(rh) +
>GENL_HDRLEN);
>        int alen阿伦 = rh->nlmsg_len - NLMSG_HDRLEN - GENL_HDRLEN;
>        while (alen > 0) {
>                if (a->nla_type == CTRL_ATTR_FAMILY_ID)
>                        return *(uint16_t *)((char *)a + NLA_HDRLEN);
>                int step = MY_NLA_ALIGN(a->nla_len);
>                alen -= step;
>                a = (struct nlattr *)((char *)a + step);
>        }
>        return -1;
>}
>
>static int nl_recv_ack(int fd, const char *what) {
>        char rbuf[8192];
>        int n = recv(fd, rbuf, sizeof(rbuf), 0);
>        if (n < 0) { fprintf(stderr, "recv ack (%s): %s\n", what, strerror(errno));
>return -1; }
>        struct nlmsghdr *rh = (struct nlmsghdr *)rbuf;
>        if (rh->nlmsg_type == NLMSG_ERROR) {
>                struct nlmsgerr *e = NLMSG_DATA(rh);
>                if (e->error != 0)
>                        fprintf(stderr, "[%s] netlink error: %d (%s)\n", what, e->error,
>strerror(-e->error));
>                return e->error;
>        }
>        return 0;
>}
>
>static void *rta_put(char *buf, int *off, int type, const void *data, int len) {
>        struct rtattr *a = (struct rtattr *)(buf + *off);
>        a->rta_type = type;
>        a->rta_len = RTA_LENGTH(len);
>        if (len) memcpy(RTA_DATA(a), data, len);
>        *off += RTA_ALIGN(a->rta_len);
>        return a;
>}
>
>static struct rtattr *rta_nest(char *buf, int *off, int type) {
>        struct rtattr *a = (struct rtattr *)(buf + *off);
>        a->rta_type = type | NLA_F_NESTED;
>        *off += RTA_LENGTH(0);
>        return a;
>}
>
>static void rta_nest_end(char *buf, int *off, struct rtattr *start) {
>        start->rta_len = (buf + *off) - (char *)start; }
>
>static void create_veth(int fd, const char *n0, const char *n1, int peer_nsfd) {
>        char buf[2048];
>        memset(buf, 0, sizeof(buf));
>        struct nlmsghdr *nlh = (struct nlmsghdr *)buf;
>        struct ifinfomsg *ifi = NLMSG_DATA(nlh);
>        int off = NLMSG_HDRLEN + sizeof(*ifi);
>
>        nlh->nlmsg_type = RTM_NEWLINK;
>        nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE |
>NLM_F_EXCL;
>        nlh->nlmsg_seq = 10;
>        ifi->ifi_family = AF_UNSPEC;
>
>        rta_put(buf, &off, IFLA_IFNAME, n0, strlen(n0) + 1);
>        struct rtattr *linfo = rta_nest(buf, &off, IFLA_LINKINFO);
>        rta_put(buf, &off, IFLA_INFO_KIND, "veth", 5);
>        struct rtattr *idata = rta_nest(buf, &off, IFLA_INFO_DATA);
>        struct rtattr *peer = rta_nest(buf, &off, VETH_INFO_PEER);
>        struct ifinfomsg *pifi = (struct ifinfomsg *)(buf + off);
>        memset(pifi, 0, sizeof(*pifi));
>        off += sizeof(*pifi);
>        rta_put(buf, &off, IFLA_IFNAME, n1, strlen(n1) + 1);
>        rta_put(buf, &off, IFLA_NET_NS_FD, &peer_nsfd, sizeof(peer_nsfd));
>        rta_nest_end(buf, &off, peer);
>        rta_nest_end(buf, &off, idata);
>        rta_nest_end(buf, &off, linfo);
>        nlh->nlmsg_len = off;
>
>        if (send(fd, buf, nlh->nlmsg_len, 0) < 0) { perror("send veth"); exit(1); }
>        int e = nl_recv_ack(fd, "create_veth");
>        if (e && e != -EEXIST) { fprintf(stderr, "veth create failed:
>%d\n", e); exit(1); }
>}
>
>static void set_if_up(int fd, const char *name) {
>        char buf[512];
>        memset(buf, 0, sizeof(buf));
>        struct nlmsghdr *nlh = (struct nlmsghdr *)buf;
>        struct ifinfomsg *ifi = NLMSG_DATA(nlh);
>        int off = NLMSG_HDRLEN + sizeof(*ifi);
>        nlh->nlmsg_type = RTM_NEWLINK;
>        nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
>        nlh->nlmsg_seq = 20;
>        ifi->ifi_family = AF_UNSPEC;
>        ifi->ifi_index = if_nametoindex(name);
>        ifi->ifi_flags = IFF_UP;
>        ifi->ifi_change = IFF_UP;
>        nlh->nlmsg_len = off;
>        if (send(fd, buf, nlh->nlmsg_len, 0) < 0) { perror("send up"); exit(1); }
>        nl_recv_ack(fd, "set_if_up");
>}
>
>static void add_ipv4(int fd, const char *name, const char *ip, int prefix) {
>        char buf[512];
>        memset(buf, 0, sizeof(buf));
>        struct nlmsghdr *nlh = (struct nlmsghdr *)buf;
>        struct ifaddrmsg *ifa = NLMSG_DATA(nlh);
>        int off = NLMSG_HDRLEN + sizeof(*ifa);
>        nlh->nlmsg_type = RTM_NEWADDR;
>        nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE |
>NLM_F_REPLACE;
>        nlh->nlmsg_seq = 30;
>        ifa->ifa_family = AF_INET;
>        ifa->ifa_prefixlen = prefix;
>        ifa->ifa_scope = 0;
>        ifa->ifa_index = if_nametoindex(name);
>
>        struct in_addr a;
>        inet_pton(AF_INET, ip, &a);
>        rta_put(buf, &off, IFA_LOCAL, &a, 4);
>        rta_put(buf, &off, IFA_ADDRESS, &a, 4);
>        nlh->nlmsg_len = off;
>        if (send(fd, buf, nlh->nlmsg_len, 0) < 0) { perror("send addr"); exit(1); }
>        nl_recv_ack(fd, "add_ipv4");
>}
>
>static void tipc_set_node_id(int fd, uint64_t w0, uint64_t w1) {
>        char buf[1024];
>        memset(buf, 0, sizeof(buf));
>        struct nlmsghdr *nlh = (struct nlmsghdr *)buf;
>        struct genlmsghdrgenlmsghdr *gh = (struct genlmsghdrgenlmsghdr
>*)NLMSG_DATA(nlh);
>        int off = NLMSG_HDRLEN + GENL_HDRLEN;
>        nlh->nlmsg_type = tipc_family;
>        nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
>        nlh->nlmsg_seq = 40;
>        gh->cmd = TIPC_NL_NET_SET;
>        gh->version = 1;
>
>        struct nlattr *net = nla_nest_start(buf, &off, TIPC_NLA_NET);
>        nla_put(buf, &off, TIPC_NLA_NET_NODEID, &w0, sizeof(w0));
>        nla_put(buf, &off, TIPC_NLA_NET_NODEID_W1, &w1, sizeof(w1));
>        nla_nest_end(buf, &off, net);
>        nlh->nlmsg_len = off;
>
>        if (send(fd, buf, nlh->nlmsg_len, 0) < 0) { perror("send net_set"); exit(1); }
>        int e = nl_recv_ack(fd, "tipc_set_node_id");
>        if (e) fprintf(stderr, "net_set(nodeid) returned %d\n", e); }
>
>static void put_sockaddr_storage_v4(char *buf, int *off, int type, const char *ip,
>int port) {
>        struct sockaddr_storage ss;
>        memset(&ss, 0, sizeof(ss));
>        struct sockaddr_in *sin = (struct sockaddr_in *)&ss;
>        sin->sin_family = AF_INET;
>        sin->sin_port = htons(port);
>        inet_pton(AF_INET, ip, &sin->sin_addr);
>        nla_put(buf, off, type, &ss, sizeof(ss)); }
>
>static void tipc_udp_bearer(int fd, int cmd, const char *bname,
>                            const char *local_ip, const char *remote_ip) {
>        char buf[2048];
>        memset(buf, 0, sizeof(buf));
>        struct nlmsghdr *nlh = (struct nlmsghdr *)buf;
>        struct genlmsghdrgenlmsghdr *gh = (struct genlmsghdrgenlmsghdr
>*)NLMSG_DATA(nlh);
>        int off = NLMSG_HDRLEN + GENL_HDRLEN;
>        nlh->nlmsg_type = tipc_family;
>        nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
>        nlh->nlmsg_seq = 50;
>        gh->cmd = cmd;
>        gh->version = 1;
>
>        struct nlattr *bearer = nla_nest_start(buf, &off, TIPC_NLA_BEARER);
>        nla_put(buf, &off, TIPC_NLA_BEARER_NAME, bname名称,
>strlen(bname名称) + 1);
>        if (cmd == TIPC_NL_BEARER_ENABLE) {
>                struct nlattr *udp = nla_nest_start(buf, &off,
>TIPC_NLA_BEARER_UDP_OPTS);
>                put_sockaddr_storage_v4(buf, &off, TIPC_NLA_UDP_LOCAL, local_ip,
>TIPC_UDP_PORT);
>                put_sockaddr_storage_v4(buf, &off, TIPC_NLA_UDP_REMOTE,
>remote_ip, TIPC_UDP_PORT);
>                nla_nest_end(buf, &off, udp);
>        }
>        nla_nest_end(buf, &off, bearer);
>        nlh->nlmsg_len = off;
>
>        if (send(fd, buf, nlh->nlmsg_len, 0) < 0) { perror("send bearer"); exit(1); }
>        int e = nl_recv_ack(fd, "tipc_udp_bearer");
>        if (e) fprintf(stderr, "bearer cmd %d (%s) returned %d\n", cmd,
>bname名称, e); }
>
>static int write_file(const char *path, const char *val) {
>        int fd = open(path, O_WRONLY);
>        if (fd < 0) return -1;
>        int r = write(fd, val, strlen(val));
>        close(fd);
>        return r;
>}
>
>static int bindfill_node_scope(int target) {
>        int fd = socket(AF_TIPC, SOCK_RDM, 0);
>        if (fd < 0) { perror("socket(AF_TIPC)"); exit(1); }
>        int ok = 0, fail = 0;
>        for (int i = 0; i < target + 1000; i++) {
>                struct sockaddr_tipc sa;
>                memset(&sa, 0, sizeof(sa));
>                sa.family = AF_TIPC;
>                sa.addrtype = TIPC_SERVICE_ADDR;
>                sa.scope = TIPC_NODE_SCOPE;
>                sa.addr.nameseq.type = 100000 + i;
>                sa.addr.nameseq.lower = 100000 + i;
>                sa.addr.nameseq.upper = 100000 + i;
>                if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) == 0) {
>                        ok++;
>                        fail = 0;
>                } else if (++fail > 100) {
>                        break;
>                }
>        }
>        fprintf(stderr, "[*] bound %d node-scope publications (cap reached)\n",
>ok);
>        if (ok < 65000) { fprintf(stderr, "bindfill shortfall\n"); exit(1); }
>        return fd;
>}
>
>static void child_main(int sync_rd, int sync_wr) {
>        char c;
>        write(sync_wr, "R", 1);
>        if (read(sync_rd, &c, 1) != 1) _exit(1);
>
>        int rfd = nl_open(NETLINK_ROUTE);
>        set_if_up(rfd, "lo");
>        set_if_up(rfd, "veth1");
>        add_ipv4(rfd, "veth1", "10.0.0.2", 24);
>
>        int gfd = nl_open(NETLINK_GENERIC);
>        tipc_family = genl_resolve_family(gfd, TIPC_GENL_V2_NAME);
>        if (tipc_family < 0) _exit(1);
>
>        int bfd = bindfill_node_scope(65535);
>        tipc_set_node_id(gfd, 0x2222222222222222ULL,
>0x2222222222222222ULL);
>        tipc_udp_bearer(gfd, TIPC_NL_BEARER_ENABLE, "udp:b1", "10.0.0.2",
>"10.0.0.1");
>        write(sync_wr, "C", 1);
>
>        sleep(35);
>        tipc_udp_bearer(gfd, TIPC_NL_BEARER_DISABLE, "udp:b1", NULL, NULL);
>        fprintf(stderr, "[child] b1 disabled, keeping it down\n");
>        (void)bfd;
>        for (;;) pause();
>}
>
>int main(void)
>{
>        setvbuf(stdout, NULL, _IONBF, 0);
>        setvbuf(stderr, NULL, _IONBF, 0);
>
>        uid_t uid = getuid();
>        gid_t gid = getgid();
>
>        if (unshare(CLONE_NEWUSER | CLONE_NEWNET) == 0) {
>                write_file("/proc/self/setgroups", "deny");
>                char m[64];
>                snprintf(m, sizeof(m), "0 %d 1", uid);
>                write_file("/proc/self/uid_map", m);
>                snprintf(m, sizeof(m), "0 %d 1", gid);
>                write_file("/proc/self/gid_map", m);
>        } else if (unshare(CLONE_NEWNET) < 0) {
>                perror("unshare");
>                return 1;
>        }
>
>        int p2c[2], c2p[2];
>        if (pipe(p2c) < 0 || pipe(c2p) < 0) { perror("pipe"); return 1; }
>
>        pid_t pid = fork();
>        if (pid == 0) {
>                close(p2c[1]);
>                close(c2p[0]);
>                if (unshare(CLONE_NEWNET) < 0) _exit(1);
>                child_main(p2c[0], c2p[1]);
>                _exit(0);
>        }
>        close(p2c[0]);
>        close(c2p[1]);
>
>        char c;
>        if (read(c2p[0], &c, 1) != 1) { fprintf(stderr, "child not ready\n"); return 1; }
>
>        char nspath[64];
>        snprintf(nspath, sizeof(nspath), "/proc/%d/ns/net", pid);
>        int nsfd = open(nspath, O_RDONLY);
>        if (nsfd < 0) { perror("open ns"); return 1; }
>
>        int rfd = nl_open(NETLINK_ROUTE);
>        create_veth(rfd, "veth0", "veth1", nsfd);
>        close(nsfd);
>        set_if_up(rfd, "lo");
>        set_if_up(rfd, "veth0");
>        add_ipv4(rfd, "veth0", "10.0.0.1", 24);
>        write(p2c[1], "G", 1);
>
>        int gfd = nl_open(NETLINK_GENERIC);
>        tipc_family = genl_resolve_family(gfd, TIPC_GENL_V2_NAME);
>        if (tipc_family < 0) return 1;
>        int bfd = bindfill_node_scope(65535);
>        tipc_set_node_id(gfd, 0x1111111111111111ULL,
>0x1111111111111111ULL);
>
>        if (read(c2p[0], &c, 1) != 1)
>                fprintf(stderr, "child config sync missed\n");
>        tipc_udp_bearer(gfd, TIPC_NL_BEARER_ENABLE, "udp:b0", "10.0.0.1",
>"10.0.0.2");
>
>        fprintf(stderr, "[*] nodes configured; waiting for stale cleanup\n");
>        (void)bfd;
>        sleep(400);
>        kill(pid, SIGKILL);
>        waitpid(pid, NULL, 0);
>        fprintf(stderr, "[*] done\n");
>        return 0;
>}
>
>```
>
>
>Do you think tipc_node_cleanup() should go through the same wake logic as
>tipc_node_delete()? Happy to test the next version.

Thanks for your test report and reproducer. It will look into the reproducer to fix outstanding issues.

>
>Thanks,
>Weiming

^ permalink raw reply

* Re: [PATCH 2/2] net/sock: Propagate WF_SYNC only when requested
From: Shrikanth Hegde @ 2026-07-21  4:50 UTC (permalink / raw)
  To: Srikar Dronamraju, LKML, netdev, David S Miller
  Cc: Ingo Molnar, Peter Zijlstra, Dietmar Eggemann, Dust Li, D Wythe,
	Eric Dumazet, Jakub Kicinski, Jon Maloy, Kuniyuki Iwashima,
	linux-sctp, Mahanta Jambigi, Marcelo Ricardo Leitner, Paolo Abeni,
	Sidraya Jayagond, Simon Horman, Tony Lu, Wen Gu, Wenjia Zhang,
	Willem de Bruijn, Xin Long, Vincent Guittot, Steven Rostedt,
	Ben Segall, Mel Gorman, Valentin Schneider, K Prateek Nayak
In-Reply-To: <20260714013940.4068189-6-srikar@linux.ibm.com>

Hi Srikar,

On 7/14/26 7:09 AM, Srikar Dronamraju wrote:
> Use SOCK_SYNC_WAKEUP to select between synchronous and asynchronous wakeup
> wakeup APIs. This avoids propagating WF_SYNC when no blocking waiter is
> expected. All wakeup locations in networking code that currently issue
> synchronous poll-style wakeups unconditionally are updated.
> 

You can also add the performance data in the cover-letter to this patch.

> Signed-off-by: Srikar Dronamraju <srikar@linux.ibm.com>
> ---
>   net/core/sock.c    | 31 ++++++++++++++++++++++++-------
>   net/sctp/socket.c  | 10 ++++++++--
>   net/smc/af_smc.c   |  4 ++--
>   net/smc/smc_rx.c   | 10 ++++++++--
>   net/tipc/socket.c  | 22 +++++++++++++++++-----
>   net/unix/af_unix.c | 26 ++++++++++++++++++--------
>   6 files changed, 77 insertions(+), 26 deletions(-)
> 
> diff --git a/net/core/sock.c b/net/core/sock.c
> index 8a59bfaa8096..a214e883b14b 100644
> --- a/net/core/sock.c
> +++ b/net/core/sock.c
> @@ -3652,9 +3652,15 @@ void sock_def_readable(struct sock *sk)
>   
>   	rcu_read_lock();
>   	wq = rcu_dereference(sk->sk_wq);
> -	if (skwq_has_sleeper(wq))
> -		wake_up_interruptible_sync_poll(&wq->wait, EPOLLIN | EPOLLPRI |
> +	if (skwq_has_sleeper(wq)) {
> +		if (sock_flag(sk, SOCK_SYNC_WAKEUP)) {
> +			wake_up_interruptible_sync_poll(&wq->wait, EPOLLIN | EPOLLPRI |
> +						EPOLLRDNORM | EPOLLRDBAND);
> +		} else {
> +			wake_up_interruptible_poll(&wq->wait, EPOLLIN | EPOLLPRI |
>   						EPOLLRDNORM | EPOLLRDBAND);
> +		}
> +	}
>   	sk_wake_async_rcu(sk, SOCK_WAKE_WAITD, POLL_IN);
>   	rcu_read_unlock();
>   }
> @@ -3670,9 +3676,15 @@ static void sock_def_write_space(struct sock *sk)
>   	 */
>   	if (sock_writeable(sk)) {
>   		wq = rcu_dereference(sk->sk_wq);
> -		if (skwq_has_sleeper(wq))
> -			wake_up_interruptible_sync_poll(&wq->wait, EPOLLOUT |
> +		if (skwq_has_sleeper(wq)) {
> +			if (sock_flag(sk, SOCK_SYNC_WAKEUP)) {
> +				wake_up_interruptible_sync_poll(&wq->wait, EPOLLOUT |
> +						EPOLLWRNORM | EPOLLWRBAND);
> +			} else {
> +				wake_up_interruptible_poll(&wq->wait, EPOLLOUT |
>   						EPOLLWRNORM | EPOLLWRBAND);
> +			}
> +		}
>   
>   		/* Should agree with poll, otherwise some programs break */
>   		sk_wake_async_rcu(sk, SOCK_WAKE_SPACE, POLL_OUT);
> @@ -3695,10 +3707,15 @@ static void sock_def_write_space_wfree(struct sock *sk, int wmem_alloc)
>   
>   		/* rely on refcount_sub from sock_wfree() */
>   		smp_mb__after_atomic();
> -		if (wq && waitqueue_active(&wq->wait))
> -			wake_up_interruptible_sync_poll(&wq->wait, EPOLLOUT |
> +		if (wq && waitqueue_active(&wq->wait)) {
> +			if (sock_flag(sk, SOCK_SYNC_WAKEUP)) {
> +				wake_up_interruptible_sync_poll(&wq->wait, EPOLLOUT |
>   						EPOLLWRNORM | EPOLLWRBAND);
> -
> +			} else {
> +				wake_up_interruptible_poll(&wq->wait, EPOLLOUT |
> +						EPOLLWRNORM | EPOLLWRBAND);
> +			}
> +		}
>   		/* Should agree with poll, otherwise some programs break */
>   		sk_wake_async_rcu(sk, SOCK_WAKE_SPACE, POLL_OUT);
>   	}
> diff --git a/net/sctp/socket.c b/net/sctp/socket.c
> index c7b9e325ec1c..9cb3432f065a 100644
> --- a/net/sctp/socket.c
> +++ b/net/sctp/socket.c
> @@ -9348,9 +9348,15 @@ void sctp_data_ready(struct sock *sk)
>   
>   	rcu_read_lock();
>   	wq = rcu_dereference(sk->sk_wq);
> -	if (skwq_has_sleeper(wq))
> -		wake_up_interruptible_sync_poll(&wq->wait, EPOLLIN |
> +	if (skwq_has_sleeper(wq)) {
> +		if (sock_flag(sk, SOCK_SYNC_WAKEUP)) {
> +			wake_up_interruptible_sync_poll(&wq->wait, EPOLLIN |
> +					EPOLLRDNORM | EPOLLRDBAND);
> +		} else {
> +			wake_up_interruptible_poll(&wq->wait, EPOLLIN |
>   						EPOLLRDNORM | EPOLLRDBAND);
> +		}
> +	}
>   	sk_wake_async_rcu(sk, SOCK_WAKE_WAITD, POLL_IN);
>   	rcu_read_unlock();
>   }
> diff --git a/net/smc/af_smc.c b/net/smc/af_smc.c
> index b5db69073e20..1a6ea2e30769 100644
> --- a/net/smc/af_smc.c
> +++ b/net/smc/af_smc.c
> @@ -819,10 +819,10 @@ static void smc_fback_wakeup_waitqueue(struct smc_sock *smc, void *key)
>   		wake_up_interruptible_all(&wq->wait);
>   	} else {
>   		flags = key_to_poll(key);
> -		if (flags & (EPOLLIN | EPOLLOUT))
> +		if (flags & (EPOLLIN | EPOLLOUT) && sock_flag(&smc->sk, SOCK_SYNC_WAKEUP))
>   			/* sk_data_ready or sk_write_space */
>   			wake_up_interruptible_sync_poll(&wq->wait, flags);
> -		else if (flags & EPOLLERR)
> +		else
>   			/* sk_error_report */
>   			wake_up_interruptible_poll(&wq->wait, flags);
>   	}
> diff --git a/net/smc/smc_rx.c b/net/smc/smc_rx.c
> index c1d9b923938d..4e288a2364d2 100644
> --- a/net/smc/smc_rx.c
> +++ b/net/smc/smc_rx.c
> @@ -39,9 +39,15 @@ static void smc_rx_wake_up(struct sock *sk)
>   	/* called already in smc_listen_work() */
>   	rcu_read_lock();
>   	wq = rcu_dereference(sk->sk_wq);
> -	if (skwq_has_sleeper(wq))
> -		wake_up_interruptible_sync_poll(&wq->wait, EPOLLIN | EPOLLPRI |
> +	if (skwq_has_sleeper(wq)) {
> +		if (sock_flag(sk, SOCK_SYNC_WAKEUP)) {
> +			wake_up_interruptible_sync_poll(&wq->wait, EPOLLIN | EPOLLPRI |
>   						EPOLLRDNORM | EPOLLRDBAND);
> +		} else {
> +			wake_up_interruptible_poll(&wq->wait, EPOLLIN | EPOLLPRI |
> +						EPOLLRDNORM | EPOLLRDBAND);
> +		}
> +	}
>   	sk_wake_async_rcu(sk, SOCK_WAKE_WAITD, POLL_IN);
>   	if ((sk->sk_shutdown == SHUTDOWN_MASK) ||
>   	    (sk->sk_state == SMC_CLOSED))
> diff --git a/net/tipc/socket.c b/net/tipc/socket.c
> index e564341e0216..9fa83a89882c 100644
> --- a/net/tipc/socket.c
> +++ b/net/tipc/socket.c
> @@ -2116,9 +2116,15 @@ static void tipc_write_space(struct sock *sk)
>   
>   	rcu_read_lock();
>   	wq = rcu_dereference(sk->sk_wq);
> -	if (skwq_has_sleeper(wq))
> -		wake_up_interruptible_sync_poll(&wq->wait, EPOLLOUT |
> +	if (skwq_has_sleeper(wq)) {
> +		if (sock_flag(sk, SOCK_SYNC_WAKEUP)) {
> +			wake_up_interruptible_sync_poll(&wq->wait, EPOLLOUT |
>   						EPOLLWRNORM | EPOLLWRBAND);
> +		} else {
> +			wake_up_interruptible_poll(&wq->wait, EPOLLOUT |
> +						EPOLLWRNORM | EPOLLWRBAND);
> +		}
> +	}
>   	rcu_read_unlock();
>   }
>   
> @@ -2134,9 +2140,15 @@ static void tipc_data_ready(struct sock *sk)
>   
>   	rcu_read_lock();
>   	wq = rcu_dereference(sk->sk_wq);
> -	if (skwq_has_sleeper(wq))
> -		wake_up_interruptible_sync_poll(&wq->wait, EPOLLIN |
> -						EPOLLRDNORM | EPOLLRDBAND);
> +	if (skwq_has_sleeper(wq)) {
> +		if (sock_flag(sk, SOCK_SYNC_WAKEUP)) {
> +			wake_up_interruptible_sync_poll(&wq->wait, EPOLLIN |
> +							EPOLLRDNORM | EPOLLRDBAND);
> +		} else {
> +			wake_up_interruptible_poll(&wq->wait, EPOLLIN |
> +							EPOLLRDNORM | EPOLLRDBAND);
> +		}
> +	}
>   	rcu_read_unlock();
>   }
>   
> diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
> index f7a9d55eee8a..15ebcc2d9d58 100644
> --- a/net/unix/af_unix.c
> +++ b/net/unix/af_unix.c
> @@ -601,9 +601,15 @@ static void unix_write_space(struct sock *sk)
>   	rcu_read_lock();
>   	if (unix_writable(sk, READ_ONCE(sk->sk_state))) {
>   		wq = rcu_dereference(sk->sk_wq);
> -		if (skwq_has_sleeper(wq))
> -			wake_up_interruptible_sync_poll(&wq->wait,
> -				EPOLLOUT | EPOLLWRNORM | EPOLLWRBAND);
> +		if (skwq_has_sleeper(wq)) {
> +			if (sock_flag(sk, SOCK_SYNC_WAKEUP)) {
> +				wake_up_interruptible_sync_poll(&wq->wait,
> +					EPOLLOUT | EPOLLWRNORM | EPOLLWRBAND);
> +			} else {
> +				wake_up_interruptible_poll(&wq->wait,
> +					EPOLLOUT | EPOLLWRNORM | EPOLLWRBAND);
> +			}
> +		}
>   		sk_wake_async_rcu(sk, SOCK_WAKE_SPACE, POLL_OUT);
>   	}
>   	rcu_read_unlock();
> @@ -2603,11 +2609,15 @@ int __unix_dgram_recvmsg(struct sock *sk, struct msghdr *msg, size_t size,
>   		goto out;
>   	}
>   
> -	if (wq_has_sleeper(&u->peer_wait))
> -		wake_up_interruptible_sync_poll(&u->peer_wait,
> -						EPOLLOUT | EPOLLWRNORM |
> -						EPOLLWRBAND);
> -
> +	if (wq_has_sleeper(&u->peer_wait)) {
> +		if (sock_flag(sk, SOCK_SYNC_WAKEUP)) {
> +			wake_up_interruptible_sync_poll(&u->peer_wait,
> +						EPOLLOUT | EPOLLWRNORM | EPOLLWRBAND);
> +		} else {
> +			wake_up_interruptible_poll(&u->peer_wait,
> +						EPOLLOUT | EPOLLWRNORM | EPOLLWRBAND);
> +		}
> +	}
>   	if (msg->msg_name) {
>   		unix_copy_addr(msg, skb->sk);
>   


Would it make sense to write a macro or a wrapper function do the
same instead of sprinkling the same at all the places?

similar comment for patch 1.

IMHO, it would make it easier to read.


^ permalink raw reply

* Re: [PATCH 1/2] net/socket: Record preference for synchronous wakeups
From: Shrikanth Hegde @ 2026-07-21  5:00 UTC (permalink / raw)
  To: Srikar Dronamraju, LKML, netdev, David S Miller
  Cc: Ingo Molnar, Peter Zijlstra, Dietmar Eggemann, Dust Li, D Wythe,
	Eric Dumazet, Jakub Kicinski, Jon Maloy, Kuniyuki Iwashima,
	linux-sctp, Mahanta Jambigi, Marcelo Ricardo Leitner, Paolo Abeni,
	Sidraya Jayagond, Simon Horman, Tony Lu, Wen Gu, Wenjia Zhang,
	Willem de Bruijn, Xin Long, Vincent Guittot, Steven Rostedt,
	Ben Segall, Mel Gorman, Valentin Schneider, K Prateek Nayak
In-Reply-To: <20260714013940.4068189-5-srikar@linux.ibm.com>

Hi Srikar.

On 7/14/26 7:09 AM, Srikar Dronamraju wrote:
> Scheduler differentiates between affine and non-affine wakeups by the
> way of sync flags. Scheduler prefers to pull the tasks towards the waker
> if the sync flag is set.
> 
> In some cases, socket APIs are blindly requesting sync wakeups. This may
> cause load-balance issues and non-optimal performance.
> 
> Record whether the most recent blocking socket operation could benefit
> from synchronous wakeups. Subsequent readiness notifications use this
> hint to determine whether WF_SYNC should be propagated.
> 

What you mean by recent? Was it info on past set of packets?
Could you please explain the flow a bit?

Shouldn't it be
- if this socket has been defined as nonblock it shouldn't use sync
   always?

> The flag is advisory and affects only wakeup placement decisions.
> 
> Signed-off-by: Srikar Dronamraju <srikar@linux.ibm.com>
> ---
>   include/net/sock.h |  1 +
>   net/socket.c       | 56 +++++++++++++++++++++++++++++++++++++++-------
>   2 files changed, 49 insertions(+), 8 deletions(-)
> 
> diff --git a/include/net/sock.h b/include/net/sock.h
> index 51185222aac2..acc6b1976dc4 100644
> --- a/include/net/sock.h
> +++ b/include/net/sock.h
> @@ -1022,6 +1022,7 @@ enum sock_flags {
>   	SOCK_RCVMARK, /* Receive SO_MARK  ancillary data with packet */
>   	SOCK_RCVPRIORITY, /* Receive SO_PRIORITY ancillary data with packet */
>   	SOCK_TIMESTAMPING_ANY, /* Copy of sk_tsflags & TSFLAGS_ANY */
> +	SOCK_SYNC_WAKEUP, /* Prefer synchronous socket wakeups */
>   };
>   
>   #define SK_FLAGS_TIMESTAMP ((1UL << SOCK_TIMESTAMP) | (1UL << SOCK_TIMESTAMPING_RX_SOFTWARE))
> diff --git a/net/socket.c b/net/socket.c
> index 63c69a0fa74e..0bcb57ae490e 100644
> --- a/net/socket.c
> +++ b/net/socket.c
> @@ -1198,15 +1198,27 @@ static void sock_splice_eof(struct file *file)
>   		ops->splice_eof(sock);
>   }
>   
> +static inline void sock_update_sync_wakeup(struct sock *sk, bool nonblock)
> +{
> +	if (unlikely(!sk))
> +		return;
> +
> +	if (nonblock) {
> +		if (sock_flag(sk, SOCK_SYNC_WAKEUP))
> +			sock_reset_flag(sk, SOCK_SYNC_WAKEUP);
> +	} else {
> +		if (!sock_flag(sk, SOCK_SYNC_WAKEUP))
> +			sock_set_flag(sk, SOCK_SYNC_WAKEUP);
> +	}
> +}

nit: You can combine two if statements. A bit easier to read.


static inline void sock_update_sync_wakeup(struct sock *sk, bool nonblock)
{
	if (unlikely(!sk))
		return;

	if (nonblock && sock_flag(sk, SOCK_SYNC_WAKEUP))
		sock_reset_flag(sk, SOCK_SYNC_WAKEUP);
	else if (!nonblock && !sock_flag(sk, SOCK_SYNC_WAKEUP))
		sock_set_flag(sk, SOCK_SYNC_WAKEUP);
}

> +
>   static ssize_t sock_read_iter(struct kiocb *iocb, struct iov_iter *to)
>   {
>   	struct file *file = iocb->ki_filp;
>   	struct socket *sock = file->private_data;
>   	struct msghdr msg = {.msg_iter = *to};
>   	ssize_t res;
> -
> -	if (file->f_flags & O_NONBLOCK || (iocb->ki_flags & IOCB_NOWAIT))
> -		msg.msg_flags = MSG_DONTWAIT;
> +	bool nonblock;
>   
>   	if (iocb->ki_pos != 0)
>   		return -ESPIPE;
> @@ -1214,6 +1226,11 @@ static ssize_t sock_read_iter(struct kiocb *iocb, struct iov_iter *to)
>   	if (!iov_iter_count(to))	/* Match SYS5 behaviour */
>   		return 0;
>   
> +	nonblock = (file->f_flags & O_NONBLOCK) || (iocb->ki_flags & IOCB_NOWAIT);
> +	if (nonblock)
> +		msg.msg_flags = MSG_DONTWAIT;
> +
> +	sock_update_sync_wakeup(sock->sk, nonblock);
>   	res = sock_recvmsg(sock, &msg, msg.msg_flags);
>   	*to = msg.msg_iter;
>   	return res;
> @@ -1225,13 +1242,17 @@ static ssize_t sock_write_iter(struct kiocb *iocb, struct iov_iter *from)
>   	struct socket *sock = file->private_data;
>   	struct msghdr msg = {.msg_iter = *from};
>   	ssize_t res;
> +	bool nonblock;
>   
>   	if (iocb->ki_pos != 0)
>   		return -ESPIPE;
>   
> -	if (file->f_flags & O_NONBLOCK || (iocb->ki_flags & IOCB_NOWAIT))
> +	nonblock = (file->f_flags & O_NONBLOCK) || (iocb->ki_flags & IOCB_NOWAIT);
> +	if (nonblock)
>   		msg.msg_flags = MSG_DONTWAIT;
>   
> +	sock_update_sync_wakeup(sock->sk, nonblock);
> +
>   	if (sock->type == SOCK_SEQPACKET)
>   		msg.msg_flags |= MSG_EOR;
>   
> @@ -2221,6 +2242,7 @@ int __sys_sendto(int fd, void __user *buff, size_t len, unsigned int flags,
>   	struct sockaddr_storage address;
>   	int err;
>   	struct msghdr msg;
> +	bool nonblock;
>   
>   	err = import_ubuf(ITER_SOURCE, buff, len, &msg.msg_iter);
>   	if (unlikely(err))
> @@ -2246,8 +2268,11 @@ int __sys_sendto(int fd, void __user *buff, size_t len, unsigned int flags,
>   		msg.msg_namelen = addr_len;
>   	}
>   	flags &= ~MSG_INTERNAL_SENDMSG_FLAGS;
> -	if (sock->file->f_flags & O_NONBLOCK)
> +	nonblock = (sock->file->f_flags & O_NONBLOCK);
> +	if (nonblock)
>   		flags |= MSG_DONTWAIT;
> +
> +	sock_update_sync_wakeup(sock->sk, nonblock);
>   	msg.msg_flags = flags;
>   	return __sock_sendmsg(sock, &msg);
>   }
> @@ -2284,6 +2309,7 @@ int __sys_recvfrom(int fd, void __user *ubuf, size_t size, unsigned int flags,
>   	};
>   	struct socket *sock;
>   	int err, err2;
> +	bool nonblock;
>   
>   	err = import_ubuf(ITER_DEST, ubuf, size, &msg.msg_iter);
>   	if (unlikely(err))
> @@ -2297,8 +2323,11 @@ int __sys_recvfrom(int fd, void __user *ubuf, size_t size, unsigned int flags,
>   	if (unlikely(!sock))
>   		return -ENOTSOCK;
>   
> -	if (sock->file->f_flags & O_NONBLOCK)
> +	nonblock = (sock->file->f_flags & O_NONBLOCK);
> +	if (nonblock)
>   		flags |= MSG_DONTWAIT;
> +
> +	sock_update_sync_wakeup(sock->sk, nonblock);
>   	err = sock_recvmsg(sock, &msg, flags);
>   
>   	if (err >= 0 && addr != NULL) {
> @@ -2634,6 +2663,7 @@ static int ____sys_sendmsg(struct socket *sock, struct msghdr *msg_sys,
>   	unsigned char *ctl_buf = ctl;
>   	int ctl_len;
>   	ssize_t err;
> +	bool nonblock;
>   
>   	err = -ENOBUFS;
>   
> @@ -2666,8 +2696,12 @@ static int ____sys_sendmsg(struct socket *sock, struct msghdr *msg_sys,
>   	flags &= ~MSG_INTERNAL_SENDMSG_FLAGS;
>   	msg_sys->msg_flags = flags;
>   
> -	if (sock->file->f_flags & O_NONBLOCK)
> +	nonblock = (sock->file->f_flags & O_NONBLOCK);
> +	if (nonblock)
>   		msg_sys->msg_flags |= MSG_DONTWAIT;
> +
> +	sock_update_sync_wakeup(sock->sk, nonblock);
> +
>   	/*
>   	 * If this is sendmmsg() and current destination address is same as
>   	 * previously succeeded address, omit asking LSM's decision.
> @@ -2887,6 +2921,7 @@ static int ____sys_recvmsg(struct socket *sock, struct msghdr *msg_sys,
>   	unsigned long cmsg_ptr;
>   	int len;
>   	ssize_t err;
> +	bool nonblock;
>   
>   	msg_sys->msg_name = &addr;
>   	cmsg_ptr = (unsigned long)msg_sys->msg_control;
> @@ -2895,9 +2930,12 @@ static int ____sys_recvmsg(struct socket *sock, struct msghdr *msg_sys,
>   	/* We assume all kernel code knows the size of sockaddr_storage */
>   	msg_sys->msg_namelen = 0;
>   
> -	if (sock->file->f_flags & O_NONBLOCK)
> +	nonblock = (sock->file->f_flags & O_NONBLOCK);
> +	if (nonblock)
>   		flags |= MSG_DONTWAIT;
>   
> +	sock_update_sync_wakeup(sock->sk, nonblock);
> +
>   	if (unlikely(nosec))
>   		err = sock_recvmsg_nosec(sock, msg_sys, flags);
>   	else
> @@ -3056,6 +3094,8 @@ static int do_recvmmsg(int fd, struct mmsghdr __user *mmsg,
>   		if (flags & MSG_WAITFORONE)
>   			flags |= MSG_DONTWAIT;
>   
> +		sock_update_sync_wakeup(sock->sk, flags & MSG_WAITFORONE);
> +
>   		if (timeout) {
>   			ktime_get_ts64(&timeout64);
>   			*timeout = timespec64_sub(end_time, timeout64);


^ permalink raw reply


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