Răsfoiți Sursa

kernel: backport pppoe improvements

Backport PPP patches accepted upstream.

Manually rebased:
- target/linux/ipq40xx/patches-6.12/999-atm-mpoa-intel-dsl-phy-support.patch

Signed-off-by: Qingfang Deng <[email protected]>
Link: https://github.com/openwrt/openwrt/pull/21892
Signed-off-by: Hauke Mehrtens <[email protected]>
Qingfang Deng 5 zile în urmă
părinte
comite
316492b809

+ 126 - 0
target/linux/generic/backport-6.12/621-v6.17-ppp-convert-to-percpu-netstats.patch

@@ -0,0 +1,126 @@
+From 1a3e9b7a6b09e8ab3d2af019e4a392622685855e Mon Sep 17 00:00:00 2001
+From: Qingfang Deng <[email protected]>
+Date: Tue, 10 Jun 2025 16:32:10 +0800
+Subject: [PATCH] ppp: convert to percpu netstats
+
+Convert to percpu netstats to avoid lock contention when reading them.
+
+Signed-off-by: Qingfang Deng <[email protected]>
+Link: https://patch.msgid.link/[email protected]
+Signed-off-by: Jakub Kicinski <[email protected]>
+---
+ drivers/net/ppp/ppp_generic.c | 52 +++++++++++++----------------------
+ 1 file changed, 19 insertions(+), 33 deletions(-)
+
+--- a/drivers/net/ppp/ppp_generic.c
++++ b/drivers/net/ppp/ppp_generic.c
+@@ -108,18 +108,6 @@ struct ppp_file {
+ #define PF_TO_CHANNEL(pf)	PF_TO_X(pf, struct channel)
+ 
+ /*
+- * Data structure to hold primary network stats for which
+- * we want to use 64 bit storage.  Other network stats
+- * are stored in dev->stats of the ppp strucute.
+- */
+-struct ppp_link_stats {
+-	u64 rx_packets;
+-	u64 tx_packets;
+-	u64 rx_bytes;
+-	u64 tx_bytes;
+-};
+-
+-/*
+  * Data structure describing one ppp unit.
+  * A ppp unit corresponds to a ppp network interface device
+  * and represents a multilink bundle.
+@@ -162,7 +150,6 @@ struct ppp {
+ 	struct bpf_prog *active_filter; /* filter for pkts to reset idle */
+ #endif /* CONFIG_PPP_FILTER */
+ 	struct net	*ppp_net;	/* the net we belong to */
+-	struct ppp_link_stats stats64;	/* 64 bit network stats */
+ };
+ 
+ /*
+@@ -1541,23 +1528,12 @@ ppp_net_siocdevprivate(struct net_device
+ static void
+ ppp_get_stats64(struct net_device *dev, struct rtnl_link_stats64 *stats64)
+ {
+-	struct ppp *ppp = netdev_priv(dev);
+-
+-	ppp_recv_lock(ppp);
+-	stats64->rx_packets = ppp->stats64.rx_packets;
+-	stats64->rx_bytes   = ppp->stats64.rx_bytes;
+-	ppp_recv_unlock(ppp);
+-
+-	ppp_xmit_lock(ppp);
+-	stats64->tx_packets = ppp->stats64.tx_packets;
+-	stats64->tx_bytes   = ppp->stats64.tx_bytes;
+-	ppp_xmit_unlock(ppp);
+-
+ 	stats64->rx_errors        = dev->stats.rx_errors;
+ 	stats64->tx_errors        = dev->stats.tx_errors;
+ 	stats64->rx_dropped       = dev->stats.rx_dropped;
+ 	stats64->tx_dropped       = dev->stats.tx_dropped;
+ 	stats64->rx_length_errors = dev->stats.rx_length_errors;
++	dev_fetch_sw_netstats(stats64, dev->tstats);
+ }
+ 
+ static int ppp_dev_init(struct net_device *dev)
+@@ -1655,6 +1631,7 @@ static void ppp_setup(struct net_device
+ 	dev->type = ARPHRD_PPP;
+ 	dev->flags = IFF_POINTOPOINT | IFF_NOARP | IFF_MULTICAST;
+ 	dev->priv_destructor = ppp_dev_priv_destructor;
++	dev->pcpu_stat_type = NETDEV_PCPU_STAT_TSTATS;
+ 	netif_keep_dst(dev);
+ }
+ 
+@@ -1800,8 +1777,7 @@ ppp_send_frame(struct ppp *ppp, struct s
+ #endif /* CONFIG_PPP_FILTER */
+ 	}
+ 
+-	++ppp->stats64.tx_packets;
+-	ppp->stats64.tx_bytes += skb->len - PPP_PROTO_LEN;
++	dev_sw_netstats_tx_add(ppp->dev, 1, skb->len - PPP_PROTO_LEN);
+ 
+ 	switch (proto) {
+ 	case PPP_IP:
+@@ -2479,8 +2455,7 @@ ppp_receive_nonmp_frame(struct ppp *ppp,
+ 		break;
+ 	}
+ 
+-	++ppp->stats64.rx_packets;
+-	ppp->stats64.rx_bytes += skb->len - 2;
++	dev_sw_netstats_rx_add(ppp->dev, skb->len - PPP_PROTO_LEN);
+ 
+ 	npi = proto_to_npindex(proto);
+ 	if (npi < 0) {
+@@ -3308,14 +3283,25 @@ static void
+ ppp_get_stats(struct ppp *ppp, struct ppp_stats *st)
+ {
+ 	struct slcompress *vj = ppp->vj;
++	int cpu;
+ 
+ 	memset(st, 0, sizeof(*st));
+-	st->p.ppp_ipackets = ppp->stats64.rx_packets;
++	for_each_possible_cpu(cpu) {
++		struct pcpu_sw_netstats *p = per_cpu_ptr(ppp->dev->tstats, cpu);
++		u64 rx_packets, rx_bytes, tx_packets, tx_bytes;
++
++		rx_packets = u64_stats_read(&p->rx_packets);
++		rx_bytes = u64_stats_read(&p->rx_bytes);
++		tx_packets = u64_stats_read(&p->tx_packets);
++		tx_bytes = u64_stats_read(&p->tx_bytes);
++
++		st->p.ppp_ipackets += rx_packets;
++		st->p.ppp_ibytes += rx_bytes;
++		st->p.ppp_opackets += tx_packets;
++		st->p.ppp_obytes += tx_bytes;
++	}
+ 	st->p.ppp_ierrors = ppp->dev->stats.rx_errors;
+-	st->p.ppp_ibytes = ppp->stats64.rx_bytes;
+-	st->p.ppp_opackets = ppp->stats64.tx_packets;
+ 	st->p.ppp_oerrors = ppp->dev->stats.tx_errors;
+-	st->p.ppp_obytes = ppp->stats64.tx_bytes;
+ 	if (!vj)
+ 		return;
+ 	st->vj.vjs_packets = vj->sls_o_compressed + vj->sls_o_uncompressed;

+ 334 - 0
target/linux/generic/backport-6.12/622-v6.18-ppp-remove-rwlock-usage.patch

@@ -0,0 +1,334 @@
+From b8844aab519a154808dbce15a132f3e8f1c34af6 Mon Sep 17 00:00:00 2001
+From: Qingfang Deng <[email protected]>
+Date: Fri, 22 Aug 2025 09:25:47 +0800
+Subject: [PATCH] ppp: remove rwlock usage
+
+In struct channel, the upl lock is implemented using rwlock_t,
+protecting access to pch->ppp and pch->bridge.
+
+As previously discussed on the list, using rwlock in the network fast
+path is not recommended.
+This patch replaces the rwlock with a spinlock for writers, and uses RCU
+for readers.
+
+- pch->ppp and pch->bridge are now declared as __rcu pointers.
+- Readers use rcu_dereference_bh() under rcu_read_lock_bh().
+- Writers use spin_lock() to update.
+
+Signed-off-by: Qingfang Deng <[email protected]>
+Link: https://patch.msgid.link/[email protected]
+Signed-off-by: Jakub Kicinski <[email protected]>
+---
+ drivers/net/ppp/ppp_generic.c | 120 ++++++++++++++++++----------------
+ 1 file changed, 63 insertions(+), 57 deletions(-)
+
+--- a/drivers/net/ppp/ppp_generic.c
++++ b/drivers/net/ppp/ppp_generic.c
+@@ -173,11 +173,11 @@ struct channel {
+ 	struct ppp_channel *chan;	/* public channel data structure */
+ 	struct rw_semaphore chan_sem;	/* protects `chan' during chan ioctl */
+ 	spinlock_t	downl;		/* protects `chan', file.xq dequeue */
+-	struct ppp	*ppp;		/* ppp unit we're connected to */
++	struct ppp __rcu *ppp;		/* ppp unit we're connected to */
+ 	struct net	*chan_net;	/* the net channel belongs to */
+ 	netns_tracker	ns_tracker;
+ 	struct list_head clist;		/* link in list of channels per unit */
+-	rwlock_t	upl;		/* protects `ppp' and 'bridge' */
++	spinlock_t	upl;		/* protects `ppp' and 'bridge' */
+ 	struct channel __rcu *bridge;	/* "bridged" ppp channel */
+ #ifdef CONFIG_PPP_MULTILINK
+ 	u8		avail;		/* flag used in multilink stuff */
+@@ -639,34 +639,34 @@ static struct bpf_prog *compat_ppp_get_f
+  */
+ static int ppp_bridge_channels(struct channel *pch, struct channel *pchb)
+ {
+-	write_lock_bh(&pch->upl);
+-	if (pch->ppp ||
++	spin_lock(&pch->upl);
++	if (rcu_dereference_protected(pch->ppp, lockdep_is_held(&pch->upl)) ||
+ 	    rcu_dereference_protected(pch->bridge, lockdep_is_held(&pch->upl))) {
+-		write_unlock_bh(&pch->upl);
++		spin_unlock(&pch->upl);
+ 		return -EALREADY;
+ 	}
+ 	refcount_inc(&pchb->file.refcnt);
+ 	rcu_assign_pointer(pch->bridge, pchb);
+-	write_unlock_bh(&pch->upl);
++	spin_unlock(&pch->upl);
+ 
+-	write_lock_bh(&pchb->upl);
+-	if (pchb->ppp ||
++	spin_lock(&pchb->upl);
++	if (rcu_dereference_protected(pchb->ppp, lockdep_is_held(&pchb->upl)) ||
+ 	    rcu_dereference_protected(pchb->bridge, lockdep_is_held(&pchb->upl))) {
+-		write_unlock_bh(&pchb->upl);
++		spin_unlock(&pchb->upl);
+ 		goto err_unset;
+ 	}
+ 	refcount_inc(&pch->file.refcnt);
+ 	rcu_assign_pointer(pchb->bridge, pch);
+-	write_unlock_bh(&pchb->upl);
++	spin_unlock(&pchb->upl);
+ 
+ 	return 0;
+ 
+ err_unset:
+-	write_lock_bh(&pch->upl);
++	spin_lock(&pch->upl);
+ 	/* Re-read pch->bridge with upl held in case it was modified concurrently */
+ 	pchb = rcu_dereference_protected(pch->bridge, lockdep_is_held(&pch->upl));
+ 	RCU_INIT_POINTER(pch->bridge, NULL);
+-	write_unlock_bh(&pch->upl);
++	spin_unlock(&pch->upl);
+ 	synchronize_rcu();
+ 
+ 	if (pchb)
+@@ -680,25 +680,25 @@ static int ppp_unbridge_channels(struct
+ {
+ 	struct channel *pchb, *pchbb;
+ 
+-	write_lock_bh(&pch->upl);
++	spin_lock(&pch->upl);
+ 	pchb = rcu_dereference_protected(pch->bridge, lockdep_is_held(&pch->upl));
+ 	if (!pchb) {
+-		write_unlock_bh(&pch->upl);
++		spin_unlock(&pch->upl);
+ 		return -EINVAL;
+ 	}
+ 	RCU_INIT_POINTER(pch->bridge, NULL);
+-	write_unlock_bh(&pch->upl);
++	spin_unlock(&pch->upl);
+ 
+ 	/* Only modify pchb if phcb->bridge points back to pch.
+ 	 * If not, it implies that there has been a race unbridging (and possibly
+ 	 * even rebridging) pchb.  We should leave pchb alone to avoid either a
+ 	 * refcount underflow, or breaking another established bridge instance.
+ 	 */
+-	write_lock_bh(&pchb->upl);
++	spin_lock(&pchb->upl);
+ 	pchbb = rcu_dereference_protected(pchb->bridge, lockdep_is_held(&pchb->upl));
+ 	if (pchbb == pch)
+ 		RCU_INIT_POINTER(pchb->bridge, NULL);
+-	write_unlock_bh(&pchb->upl);
++	spin_unlock(&pchb->upl);
+ 
+ 	synchronize_rcu();
+ 
+@@ -2144,10 +2144,9 @@ static int ppp_mp_explode(struct ppp *pp
+ #endif /* CONFIG_PPP_MULTILINK */
+ 
+ /* Try to send data out on a channel */
+-static void __ppp_channel_push(struct channel *pch)
++static void __ppp_channel_push(struct channel *pch, struct ppp *ppp)
+ {
+ 	struct sk_buff *skb;
+-	struct ppp *ppp;
+ 
+ 	spin_lock(&pch->downl);
+ 	if (pch->chan) {
+@@ -2166,7 +2165,6 @@ static void __ppp_channel_push(struct ch
+ 	spin_unlock(&pch->downl);
+ 	/* see if there is anything from the attached unit to be sent */
+ 	if (skb_queue_empty(&pch->file.xq)) {
+-		ppp = pch->ppp;
+ 		if (ppp)
+ 			__ppp_xmit_process(ppp, NULL);
+ 	}
+@@ -2174,15 +2172,18 @@ static void __ppp_channel_push(struct ch
+ 
+ static void ppp_channel_push(struct channel *pch)
+ {
+-	read_lock_bh(&pch->upl);
+-	if (pch->ppp) {
+-		(*this_cpu_ptr(pch->ppp->xmit_recursion))++;
+-		__ppp_channel_push(pch);
+-		(*this_cpu_ptr(pch->ppp->xmit_recursion))--;
++	struct ppp *ppp;
++
++	rcu_read_lock_bh();
++	ppp = rcu_dereference_bh(pch->ppp);
++	if (ppp) {
++		(*this_cpu_ptr(ppp->xmit_recursion))++;
++		__ppp_channel_push(pch, ppp);
++		(*this_cpu_ptr(ppp->xmit_recursion))--;
+ 	} else {
+-		__ppp_channel_push(pch);
++		__ppp_channel_push(pch, NULL);
+ 	}
+-	read_unlock_bh(&pch->upl);
++	rcu_read_unlock_bh();
+ }
+ 
+ /*
+@@ -2284,6 +2285,7 @@ void
+ ppp_input(struct ppp_channel *chan, struct sk_buff *skb)
+ {
+ 	struct channel *pch = chan->ppp;
++	struct ppp *ppp;
+ 	int proto;
+ 
+ 	if (!pch) {
+@@ -2295,18 +2297,19 @@ ppp_input(struct ppp_channel *chan, stru
+ 	if (ppp_channel_bridge_input(pch, skb))
+ 		return;
+ 
+-	read_lock_bh(&pch->upl);
++	rcu_read_lock_bh();
++	ppp = rcu_dereference_bh(pch->ppp);
+ 	if (!ppp_decompress_proto(skb)) {
+ 		kfree_skb(skb);
+-		if (pch->ppp) {
+-			++pch->ppp->dev->stats.rx_length_errors;
+-			ppp_receive_error(pch->ppp);
++		if (ppp) {
++			++ppp->dev->stats.rx_length_errors;
++			ppp_receive_error(ppp);
+ 		}
+ 		goto done;
+ 	}
+ 
+ 	proto = PPP_PROTO(skb);
+-	if (!pch->ppp || proto >= 0xc000 || proto == PPP_CCPFRAG) {
++	if (!ppp || proto >= 0xc000 || proto == PPP_CCPFRAG) {
+ 		/* put it on the channel queue */
+ 		skb_queue_tail(&pch->file.rq, skb);
+ 		/* drop old frames if queue too long */
+@@ -2315,11 +2318,11 @@ ppp_input(struct ppp_channel *chan, stru
+ 			kfree_skb(skb);
+ 		wake_up_interruptible(&pch->file.rwait);
+ 	} else {
+-		ppp_do_recv(pch->ppp, skb, pch);
++		ppp_do_recv(ppp, skb, pch);
+ 	}
+ 
+ done:
+-	read_unlock_bh(&pch->upl);
++	rcu_read_unlock_bh();
+ }
+ 
+ /* Put a 0-length skb in the receive queue as an error indication */
+@@ -2328,20 +2331,22 @@ ppp_input_error(struct ppp_channel *chan
+ {
+ 	struct channel *pch = chan->ppp;
+ 	struct sk_buff *skb;
++	struct ppp *ppp;
+ 
+ 	if (!pch)
+ 		return;
+ 
+-	read_lock_bh(&pch->upl);
+-	if (pch->ppp) {
++	rcu_read_lock_bh();
++	ppp = rcu_dereference_bh(pch->ppp);
++	if (ppp) {
+ 		skb = alloc_skb(0, GFP_ATOMIC);
+ 		if (skb) {
+ 			skb->len = 0;		/* probably unnecessary */
+ 			skb->cb[0] = code;
+-			ppp_do_recv(pch->ppp, skb, pch);
++			ppp_do_recv(ppp, skb, pch);
+ 		}
+ 	}
+-	read_unlock_bh(&pch->upl);
++	rcu_read_unlock_bh();
+ }
+ 
+ /*
+@@ -2889,7 +2894,6 @@ int ppp_register_net_channel(struct net
+ 
+ 	pn = ppp_pernet(net);
+ 
+-	pch->ppp = NULL;
+ 	pch->chan = chan;
+ 	pch->chan_net = get_net_track(net, &pch->ns_tracker, GFP_KERNEL);
+ 	chan->ppp = pch;
+@@ -2900,7 +2904,7 @@ int ppp_register_net_channel(struct net
+ #endif /* CONFIG_PPP_MULTILINK */
+ 	init_rwsem(&pch->chan_sem);
+ 	spin_lock_init(&pch->downl);
+-	rwlock_init(&pch->upl);
++	spin_lock_init(&pch->upl);
+ 
+ 	spin_lock_bh(&pn->all_channels_lock);
+ 	pch->file.index = ++pn->last_channel_index;
+@@ -2929,13 +2933,15 @@ int ppp_channel_index(struct ppp_channel
+ int ppp_unit_number(struct ppp_channel *chan)
+ {
+ 	struct channel *pch = chan->ppp;
++	struct ppp *ppp;
+ 	int unit = -1;
+ 
+ 	if (pch) {
+-		read_lock_bh(&pch->upl);
+-		if (pch->ppp)
+-			unit = pch->ppp->file.index;
+-		read_unlock_bh(&pch->upl);
++		rcu_read_lock();
++		ppp = rcu_dereference(pch->ppp);
++		if (ppp)
++			unit = ppp->file.index;
++		rcu_read_unlock();
+ 	}
+ 	return unit;
+ }
+@@ -2947,12 +2953,14 @@ char *ppp_dev_name(struct ppp_channel *c
+ {
+ 	struct channel *pch = chan->ppp;
+ 	char *name = NULL;
++	struct ppp *ppp;
+ 
+ 	if (pch) {
+-		read_lock_bh(&pch->upl);
+-		if (pch->ppp && pch->ppp->dev)
+-			name = pch->ppp->dev->name;
+-		read_unlock_bh(&pch->upl);
++		rcu_read_lock();
++		ppp = rcu_dereference(pch->ppp);
++		if (ppp && ppp->dev)
++			name = ppp->dev->name;
++		rcu_read_unlock();
+ 	}
+ 	return name;
+ }
+@@ -3475,9 +3483,9 @@ ppp_connect_channel(struct channel *pch,
+ 	ppp = ppp_find_unit(pn, unit);
+ 	if (!ppp)
+ 		goto out;
+-	write_lock_bh(&pch->upl);
++	spin_lock(&pch->upl);
+ 	ret = -EINVAL;
+-	if (pch->ppp ||
++	if (rcu_dereference_protected(pch->ppp, lockdep_is_held(&pch->upl)) ||
+ 	    rcu_dereference_protected(pch->bridge, lockdep_is_held(&pch->upl)))
+ 		goto outl;
+ 
+@@ -3502,13 +3510,13 @@ ppp_connect_channel(struct channel *pch,
+ 		ppp->dev->hard_header_len = hdrlen;
+ 	list_add_tail_rcu(&pch->clist, &ppp->channels);
+ 	++ppp->n_channels;
+-	pch->ppp = ppp;
++	rcu_assign_pointer(pch->ppp, ppp);
+ 	refcount_inc(&ppp->file.refcnt);
+ 	ppp_unlock(ppp);
+ 	ret = 0;
+ 
+  outl:
+-	write_unlock_bh(&pch->upl);
++	spin_unlock(&pch->upl);
+  out:
+ 	mutex_unlock(&pn->all_ppp_mutex);
+ 	return ret;
+@@ -3523,10 +3531,9 @@ ppp_disconnect_channel(struct channel *p
+ 	struct ppp *ppp;
+ 	int err = -EINVAL;
+ 
+-	write_lock_bh(&pch->upl);
+-	ppp = pch->ppp;
+-	pch->ppp = NULL;
+-	write_unlock_bh(&pch->upl);
++	spin_lock(&pch->upl);
++	ppp = rcu_replace_pointer(pch->ppp, NULL, lockdep_is_held(&pch->upl));
++	spin_unlock(&pch->upl);
+ 	if (ppp) {
+ 		/* remove it from the ppp unit's list */
+ 		ppp_lock(ppp);

+ 321 - 0
target/linux/generic/backport-6.12/623-v6.18-pppoe-remove-rwlock-usage.patch

@@ -0,0 +1,321 @@
+From 72cdc67e7fa74931b055df3a76852bab551f1a04 Mon Sep 17 00:00:00 2001
+From: Qingfang Deng <[email protected]>
+Date: Thu, 28 Aug 2025 09:20:16 +0800
+Subject: [PATCH] pppoe: remove rwlock usage
+
+Like ppp_generic.c, convert the PPPoE socket hash table to use RCU for
+lookups and a spinlock for updates. This removes rwlock usage and allows
+lockless readers on the fast path.
+
+- Mark hash table and list pointers as __rcu.
+- Use spin_lock() to protect writers.
+- Readers use rcu_dereference() under rcu_read_lock(). All known callers
+  of get_item() already hold the RCU read lock, so no additional locking
+  is needed.
+- get_item() now uses refcount_inc_not_zero() instead of sock_hold() to
+  safely take a reference. This prevents crashes if a socket is already
+  in the process of being freed (sk_refcnt == 0).
+- Set SOCK_RCU_FREE to defer socket freeing until after an RCU grace
+  period.
+- Move skb_queue_purge() into sk_destruct callback to ensure purge
+  happens after an RCU grace period.
+
+Signed-off-by: Qingfang Deng <[email protected]>
+Reviewed-by: Eric Dumazet <[email protected]>
+Link: https://patch.msgid.link/[email protected]
+Signed-off-by: Jakub Kicinski <[email protected]>
+---
+ drivers/net/ppp/pppoe.c  | 94 ++++++++++++++++++++++------------------
+ include/linux/if_pppox.h |  2 +-
+ 2 files changed, 54 insertions(+), 42 deletions(-)
+
+--- a/drivers/net/ppp/pppoe.c
++++ b/drivers/net/ppp/pppoe.c
+@@ -100,8 +100,8 @@ struct pppoe_net {
+ 	 * as well, moreover in case of SMP less locking
+ 	 * controversy here
+ 	 */
+-	struct pppox_sock *hash_table[PPPOE_HASH_SIZE];
+-	rwlock_t hash_lock;
++	struct pppox_sock __rcu *hash_table[PPPOE_HASH_SIZE];
++	spinlock_t hash_lock;
+ };
+ 
+ /*
+@@ -162,13 +162,13 @@ static struct pppox_sock *__get_item(str
+ 	int hash = hash_item(sid, addr);
+ 	struct pppox_sock *ret;
+ 
+-	ret = pn->hash_table[hash];
++	ret = rcu_dereference(pn->hash_table[hash]);
+ 	while (ret) {
+ 		if (cmp_addr(&ret->pppoe_pa, sid, addr) &&
+ 		    ret->pppoe_ifindex == ifindex)
+ 			return ret;
+ 
+-		ret = ret->next;
++		ret = rcu_dereference(ret->next);
+ 	}
+ 
+ 	return NULL;
+@@ -177,19 +177,20 @@ static struct pppox_sock *__get_item(str
+ static int __set_item(struct pppoe_net *pn, struct pppox_sock *po)
+ {
+ 	int hash = hash_item(po->pppoe_pa.sid, po->pppoe_pa.remote);
+-	struct pppox_sock *ret;
++	struct pppox_sock *ret, *first;
+ 
+-	ret = pn->hash_table[hash];
++	first = rcu_dereference_protected(pn->hash_table[hash], lockdep_is_held(&pn->hash_lock));
++	ret = first;
+ 	while (ret) {
+ 		if (cmp_2_addr(&ret->pppoe_pa, &po->pppoe_pa) &&
+ 		    ret->pppoe_ifindex == po->pppoe_ifindex)
+ 			return -EALREADY;
+ 
+-		ret = ret->next;
++		ret = rcu_dereference_protected(ret->next, lockdep_is_held(&pn->hash_lock));
+ 	}
+ 
+-	po->next = pn->hash_table[hash];
+-	pn->hash_table[hash] = po;
++	RCU_INIT_POINTER(po->next, first);
++	rcu_assign_pointer(pn->hash_table[hash], po);
+ 
+ 	return 0;
+ }
+@@ -198,20 +199,24 @@ static void __delete_item(struct pppoe_n
+ 					char *addr, int ifindex)
+ {
+ 	int hash = hash_item(sid, addr);
+-	struct pppox_sock *ret, **src;
++	struct pppox_sock *ret, __rcu **src;
+ 
+-	ret = pn->hash_table[hash];
++	ret = rcu_dereference_protected(pn->hash_table[hash], lockdep_is_held(&pn->hash_lock));
+ 	src = &pn->hash_table[hash];
+ 
+ 	while (ret) {
+ 		if (cmp_addr(&ret->pppoe_pa, sid, addr) &&
+ 		    ret->pppoe_ifindex == ifindex) {
+-			*src = ret->next;
++			struct pppox_sock *next;
++
++			next = rcu_dereference_protected(ret->next,
++							 lockdep_is_held(&pn->hash_lock));
++			rcu_assign_pointer(*src, next);
+ 			break;
+ 		}
+ 
+ 		src = &ret->next;
+-		ret = ret->next;
++		ret = rcu_dereference_protected(ret->next, lockdep_is_held(&pn->hash_lock));
+ 	}
+ }
+ 
+@@ -225,11 +230,9 @@ static inline struct pppox_sock *get_ite
+ {
+ 	struct pppox_sock *po;
+ 
+-	read_lock_bh(&pn->hash_lock);
+ 	po = __get_item(pn, sid, addr, ifindex);
+-	if (po)
+-		sock_hold(sk_pppox(po));
+-	read_unlock_bh(&pn->hash_lock);
++	if (po && !refcount_inc_not_zero(&sk_pppox(po)->sk_refcnt))
++		po = NULL;
+ 
+ 	return po;
+ }
+@@ -258,9 +261,9 @@ static inline struct pppox_sock *get_ite
+ static inline void delete_item(struct pppoe_net *pn, __be16 sid,
+ 					char *addr, int ifindex)
+ {
+-	write_lock_bh(&pn->hash_lock);
++	spin_lock(&pn->hash_lock);
+ 	__delete_item(pn, sid, addr, ifindex);
+-	write_unlock_bh(&pn->hash_lock);
++	spin_unlock(&pn->hash_lock);
+ }
+ 
+ /***************************************************************************
+@@ -276,14 +279,16 @@ static void pppoe_flush_dev(struct net_d
+ 	int i;
+ 
+ 	pn = pppoe_pernet(dev_net(dev));
+-	write_lock_bh(&pn->hash_lock);
++	spin_lock(&pn->hash_lock);
+ 	for (i = 0; i < PPPOE_HASH_SIZE; i++) {
+-		struct pppox_sock *po = pn->hash_table[i];
++		struct pppox_sock *po = rcu_dereference_protected(pn->hash_table[i],
++								  lockdep_is_held(&pn->hash_lock));
+ 		struct sock *sk;
+ 
+ 		while (po) {
+ 			while (po && po->pppoe_dev != dev) {
+-				po = po->next;
++				po = rcu_dereference_protected(po->next,
++							       lockdep_is_held(&pn->hash_lock));
+ 			}
+ 
+ 			if (!po)
+@@ -300,7 +305,7 @@ static void pppoe_flush_dev(struct net_d
+ 			 */
+ 
+ 			sock_hold(sk);
+-			write_unlock_bh(&pn->hash_lock);
++			spin_unlock(&pn->hash_lock);
+ 			lock_sock(sk);
+ 
+ 			if (po->pppoe_dev == dev &&
+@@ -320,11 +325,12 @@ static void pppoe_flush_dev(struct net_d
+ 			 */
+ 
+ 			BUG_ON(pppoe_pernet(dev_net(dev)) == NULL);
+-			write_lock_bh(&pn->hash_lock);
+-			po = pn->hash_table[i];
++			spin_lock(&pn->hash_lock);
++			po = rcu_dereference_protected(pn->hash_table[i],
++						       lockdep_is_held(&pn->hash_lock));
+ 		}
+ 	}
+-	write_unlock_bh(&pn->hash_lock);
++	spin_unlock(&pn->hash_lock);
+ }
+ 
+ static int pppoe_device_event(struct notifier_block *this,
+@@ -528,6 +534,11 @@ static struct proto pppoe_sk_proto __rea
+ 	.obj_size = sizeof(struct pppox_sock),
+ };
+ 
++static void pppoe_destruct(struct sock *sk)
++{
++	skb_queue_purge(&sk->sk_receive_queue);
++}
++
+ /***********************************************************************
+  *
+  * Initialize a new struct sock.
+@@ -542,11 +553,13 @@ static int pppoe_create(struct net *net,
+ 		return -ENOMEM;
+ 
+ 	sock_init_data(sock, sk);
++	sock_set_flag(sk, SOCK_RCU_FREE);
+ 
+ 	sock->state	= SS_UNCONNECTED;
+ 	sock->ops	= &pppoe_ops;
+ 
+ 	sk->sk_backlog_rcv	= pppoe_rcv_core;
++	sk->sk_destruct		= pppoe_destruct;
+ 	sk->sk_state		= PPPOX_NONE;
+ 	sk->sk_type		= SOCK_STREAM;
+ 	sk->sk_family		= PF_PPPOX;
+@@ -599,7 +612,6 @@ static int pppoe_release(struct socket *
+ 	sock_orphan(sk);
+ 	sock->sk = NULL;
+ 
+-	skb_queue_purge(&sk->sk_receive_queue);
+ 	release_sock(sk);
+ 	sock_put(sk);
+ 
+@@ -681,9 +693,9 @@ static int pppoe_connect(struct socket *
+ 		       &sp->sa_addr.pppoe,
+ 		       sizeof(struct pppoe_addr));
+ 
+-		write_lock_bh(&pn->hash_lock);
++		spin_lock(&pn->hash_lock);
+ 		error = __set_item(pn, po);
+-		write_unlock_bh(&pn->hash_lock);
++		spin_unlock(&pn->hash_lock);
+ 		if (error < 0)
+ 			goto err_put;
+ 
+@@ -1052,11 +1064,11 @@ static inline struct pppox_sock *pppoe_g
+ 	int i;
+ 
+ 	for (i = 0; i < PPPOE_HASH_SIZE; i++) {
+-		po = pn->hash_table[i];
++		po = rcu_dereference(pn->hash_table[i]);
+ 		while (po) {
+ 			if (!pos--)
+ 				goto out;
+-			po = po->next;
++			po = rcu_dereference(po->next);
+ 		}
+ 	}
+ 
+@@ -1065,19 +1077,19 @@ out:
+ }
+ 
+ static void *pppoe_seq_start(struct seq_file *seq, loff_t *pos)
+-	__acquires(pn->hash_lock)
++	__acquires(RCU)
+ {
+ 	struct pppoe_net *pn = pppoe_pernet(seq_file_net(seq));
+ 	loff_t l = *pos;
+ 
+-	read_lock_bh(&pn->hash_lock);
++	rcu_read_lock();
+ 	return l ? pppoe_get_idx(pn, --l) : SEQ_START_TOKEN;
+ }
+ 
+ static void *pppoe_seq_next(struct seq_file *seq, void *v, loff_t *pos)
+ {
+ 	struct pppoe_net *pn = pppoe_pernet(seq_file_net(seq));
+-	struct pppox_sock *po;
++	struct pppox_sock *po, *next;
+ 
+ 	++*pos;
+ 	if (v == SEQ_START_TOKEN) {
+@@ -1085,14 +1097,15 @@ static void *pppoe_seq_next(struct seq_f
+ 		goto out;
+ 	}
+ 	po = v;
+-	if (po->next)
+-		po = po->next;
++	next = rcu_dereference(po->next);
++	if (next)
++		po = next;
+ 	else {
+ 		int hash = hash_item(po->pppoe_pa.sid, po->pppoe_pa.remote);
+ 
+ 		po = NULL;
+ 		while (++hash < PPPOE_HASH_SIZE) {
+-			po = pn->hash_table[hash];
++			po = rcu_dereference(pn->hash_table[hash]);
+ 			if (po)
+ 				break;
+ 		}
+@@ -1103,10 +1116,9 @@ out:
+ }
+ 
+ static void pppoe_seq_stop(struct seq_file *seq, void *v)
+-	__releases(pn->hash_lock)
++	__releases(RCU)
+ {
+-	struct pppoe_net *pn = pppoe_pernet(seq_file_net(seq));
+-	read_unlock_bh(&pn->hash_lock);
++	rcu_read_unlock();
+ }
+ 
+ static const struct seq_operations pppoe_seq_ops = {
+@@ -1149,7 +1161,7 @@ static __net_init int pppoe_init_net(str
+ 	struct pppoe_net *pn = pppoe_pernet(net);
+ 	struct proc_dir_entry *pde;
+ 
+-	rwlock_init(&pn->hash_lock);
++	spin_lock_init(&pn->hash_lock);
+ 
+ 	pde = proc_create_net("pppoe", 0444, net->proc_net,
+ 			&pppoe_seq_ops, sizeof(struct seq_net_private));
+--- a/include/linux/if_pppox.h
++++ b/include/linux/if_pppox.h
+@@ -43,7 +43,7 @@ struct pppox_sock {
+ 	/* struct sock must be the first member of pppox_sock */
+ 	struct sock sk;
+ 	struct ppp_channel chan;
+-	struct pppox_sock	*next;	  /* for hash table */
++	struct pppox_sock __rcu	*next;	  /* for hash table */
+ 	union {
+ 		struct pppoe_opt pppoe;
+ 		struct pptp_opt  pptp;

+ 124 - 0
target/linux/generic/backport-6.12/624-v6.18-pppoe-drop-sock-reference-counting-on-fast-path.patch

@@ -0,0 +1,124 @@
+From 4f54dff818d7b5b1d84becd5d90bc46e6233c0d7 Mon Sep 17 00:00:00 2001
+From: Qingfang Deng <[email protected]>
+Date: Thu, 28 Aug 2025 09:20:17 +0800
+Subject: [PATCH] pppoe: drop sock reference counting on fast path
+
+Now that PPPoE sockets are freed via RCU (SOCK_RCU_FREE), it is no longer
+necessary to take a reference count when looking up sockets on the receive
+path. Readers are protected by RCU, so the socket memory remains valid
+until after a grace period.
+
+Convert fast-path lookups to avoid refcounting:
+ - Replace get_item() and sk_receive_skb() in pppoe_rcv() with
+   __get_item() and __sk_receive_skb().
+ - Rework get_item_by_addr() into __get_item_by_addr() (no refcount and
+   move RCU lock into pppoe_ioctl)
+ - Remove unnecessary sock_put() calls.
+
+This avoids cacheline bouncing from atomic reference counting and improves
+performance on the receive fast path.
+
+Signed-off-by: Qingfang Deng <[email protected]>
+Reviewed-by: Eric Dumazet <[email protected]>
+Link: https://patch.msgid.link/[email protected]
+Signed-off-by: Jakub Kicinski <[email protected]>
+---
+ drivers/net/ppp/pppoe.c | 35 +++++++++++++----------------------
+ 1 file changed, 13 insertions(+), 22 deletions(-)
+
+--- a/drivers/net/ppp/pppoe.c
++++ b/drivers/net/ppp/pppoe.c
+@@ -237,8 +237,8 @@ static inline struct pppox_sock *get_ite
+ 	return po;
+ }
+ 
+-static inline struct pppox_sock *get_item_by_addr(struct net *net,
+-						struct sockaddr_pppox *sp)
++static inline struct pppox_sock *__get_item_by_addr(struct net *net,
++						    struct sockaddr_pppox *sp)
+ {
+ 	struct net_device *dev;
+ 	struct pppoe_net *pn;
+@@ -246,15 +246,13 @@ static inline struct pppox_sock *get_ite
+ 
+ 	int ifindex;
+ 
+-	rcu_read_lock();
+ 	dev = dev_get_by_name_rcu(net, sp->sa_addr.pppoe.dev);
+ 	if (dev) {
+ 		ifindex = dev->ifindex;
+ 		pn = pppoe_pernet(net);
+-		pppox_sock = get_item(pn, sp->sa_addr.pppoe.sid,
+-				sp->sa_addr.pppoe.remote, ifindex);
++		pppox_sock = __get_item(pn, sp->sa_addr.pppoe.sid,
++					sp->sa_addr.pppoe.remote, ifindex);
+ 	}
+-	rcu_read_unlock();
+ 	return pppox_sock;
+ }
+ 
+@@ -384,18 +382,16 @@ static int pppoe_rcv_core(struct sock *s
+ 	if (sk->sk_state & PPPOX_BOUND) {
+ 		ppp_input(&po->chan, skb);
+ 	} else if (sk->sk_state & PPPOX_RELAY) {
+-		relay_po = get_item_by_addr(sock_net(sk),
+-					    &po->pppoe_relay);
++		relay_po = __get_item_by_addr(sock_net(sk),
++					      &po->pppoe_relay);
+ 		if (relay_po == NULL)
+ 			goto abort_kfree;
+ 
+ 		if ((sk_pppox(relay_po)->sk_state & PPPOX_CONNECTED) == 0)
+-			goto abort_put;
++			goto abort_kfree;
+ 
+ 		if (!__pppoe_xmit(sk_pppox(relay_po), skb))
+-			goto abort_put;
+-
+-		sock_put(sk_pppox(relay_po));
++			goto abort_kfree;
+ 	} else {
+ 		if (sock_queue_rcv_skb(sk, skb))
+ 			goto abort_kfree;
+@@ -403,9 +399,6 @@ static int pppoe_rcv_core(struct sock *s
+ 
+ 	return NET_RX_SUCCESS;
+ 
+-abort_put:
+-	sock_put(sk_pppox(relay_po));
+-
+ abort_kfree:
+ 	kfree_skb(skb);
+ 	return NET_RX_DROP;
+@@ -447,14 +440,11 @@ static int pppoe_rcv(struct sk_buff *skb
+ 	ph = pppoe_hdr(skb);
+ 	pn = pppoe_pernet(dev_net(dev));
+ 
+-	/* Note that get_item does a sock_hold(), so sk_pppox(po)
+-	 * is known to be safe.
+-	 */
+-	po = get_item(pn, ph->sid, eth_hdr(skb)->h_source, dev->ifindex);
++	po = __get_item(pn, ph->sid, eth_hdr(skb)->h_source, dev->ifindex);
+ 	if (!po)
+ 		goto drop;
+ 
+-	return sk_receive_skb(sk_pppox(po), skb, 0);
++	return __sk_receive_skb(sk_pppox(po), skb, 0, 1, false);
+ 
+ drop:
+ 	kfree_skb(skb);
+@@ -820,11 +810,12 @@ static int pppoe_ioctl(struct socket *so
+ 
+ 		/* Check that the socket referenced by the address
+ 		   actually exists. */
+-		relay_po = get_item_by_addr(sock_net(sk), &po->pppoe_relay);
++		rcu_read_lock();
++		relay_po = __get_item_by_addr(sock_net(sk), &po->pppoe_relay);
++		rcu_read_unlock();
+ 		if (!relay_po)
+ 			break;
+ 
+-		sock_put(sk_pppox(relay_po));
+ 		sk->sk_state |= PPPOX_RELAY;
+ 		err = 0;
+ 		break;

+ 104 - 0
target/linux/generic/backport-6.12/625-v7.0-ppp-enable-TX-scatter-gather.patch

@@ -0,0 +1,104 @@
+From 42fcb213e58a7da33d5d2d7517b4e521025c68c3 Mon Sep 17 00:00:00 2001
+From: Qingfang Deng <[email protected]>
+Date: Thu, 29 Jan 2026 09:29:02 +0800
+Subject: [PATCH] ppp: enable TX scatter-gather
+
+PPP channels using chan->direct_xmit prepend the PPP header to a skb and
+call dev_queue_xmit() directly. In this mode the skb does not need to be
+linear, but the PPP netdevice currently does not advertise
+scatter-gather features, causing unnecessary linearization and
+preventing GSO.
+
+Enable NETIF_F_SG and NETIF_F_FRAGLIST on PPP devices. In case a linear
+buffer is required (PPP compression, multilink, and channels without
+direct_xmit), call skb_linearize() explicitly.
+
+Signed-off-by: Qingfang Deng <[email protected]>
+Link: https://patch.msgid.link/[email protected]
+Signed-off-by: Paolo Abeni <[email protected]>
+---
+ drivers/net/ppp/ppp_generic.c | 30 +++++++++++++++++++++++++-----
+ 1 file changed, 25 insertions(+), 5 deletions(-)
+
+--- a/drivers/net/ppp/ppp_generic.c
++++ b/drivers/net/ppp/ppp_generic.c
+@@ -1632,6 +1632,8 @@ static void ppp_setup(struct net_device
+ 	dev->flags = IFF_POINTOPOINT | IFF_NOARP | IFF_MULTICAST;
+ 	dev->priv_destructor = ppp_dev_priv_destructor;
+ 	dev->pcpu_stat_type = NETDEV_PCPU_STAT_TSTATS;
++	dev->features = NETIF_F_SG | NETIF_F_FRAGLIST;
++	dev->hw_features = dev->features;
+ 	netif_keep_dst(dev);
+ }
+ 
+@@ -1696,6 +1698,10 @@ pad_compress_skb(struct ppp *ppp, struct
+ 		ppp->xcomp->comp_extra + ppp->dev->hard_header_len;
+ 	int compressor_skb_size = ppp->dev->mtu +
+ 		ppp->xcomp->comp_extra + PPP_HDRLEN;
++
++	if (skb_linearize(skb))
++		return NULL;
++
+ 	new_skb = alloc_skb(new_skb_size, GFP_ATOMIC);
+ 	if (!new_skb) {
+ 		if (net_ratelimit())
+@@ -1783,6 +1789,10 @@ ppp_send_frame(struct ppp *ppp, struct s
+ 	case PPP_IP:
+ 		if (!ppp->vj || (ppp->flags & SC_COMP_TCP) == 0)
+ 			break;
++
++		if (skb_linearize(skb))
++			goto drop;
++
+ 		/* try to do VJ TCP header compression */
+ 		new_skb = alloc_skb(skb->len + ppp->dev->hard_header_len - 2,
+ 				    GFP_ATOMIC);
+@@ -1880,19 +1890,26 @@ ppp_push(struct ppp *ppp)
+ 	}
+ 
+ 	if ((ppp->flags & SC_MULTILINK) == 0) {
++		struct ppp_channel *chan;
+ 		/* not doing multilink: send it down the first channel */
+ 		list = list->next;
+ 		pch = list_entry(list, struct channel, clist);
+ 
+ 		spin_lock(&pch->downl);
+-		if (pch->chan) {
+-			if (pch->chan->ops->start_xmit(pch->chan, skb))
+-				ppp->xmit_pending = NULL;
+-		} else {
+-			/* channel got unregistered */
++		chan = pch->chan;
++		if (unlikely(!chan || (!chan->direct_xmit && skb_linearize(skb)))) {
++			/* channel got unregistered, or it requires a linear
++			 * skb but linearization failed
++			 */
+ 			kfree_skb(skb);
+ 			ppp->xmit_pending = NULL;
++			goto out;
+ 		}
++
++		if (chan->ops->start_xmit(chan, skb))
++			ppp->xmit_pending = NULL;
++
++out:
+ 		spin_unlock(&pch->downl);
+ 		return;
+ 	}
+@@ -1977,6 +1994,8 @@ static int ppp_mp_explode(struct ppp *pp
+ 		return 0; /* can't take now, leave it in xmit_pending */
+ 
+ 	/* Do protocol field compression */
++	if (skb_linearize(skb))
++		goto err_linearize;
+ 	p = skb->data;
+ 	len = skb->len;
+ 	if (*p == 0 && mp_protocol_compress) {
+@@ -2135,6 +2154,7 @@ static int ppp_mp_explode(struct ppp *pp
+ 
+  noskb:
+ 	spin_unlock(&pch->downl);
++ err_linearize:
+ 	if (ppp->debug & 1)
+ 		netdev_err(ppp->dev, "PPP: no memory (fragment)\n");
+ 	++ppp->dev->stats.tx_errors;

+ 0 - 0
target/linux/generic/backport-6.12/625-v6.16-net-bridge-locally-receive-all-multicast-packets-if-.patch → target/linux/generic/backport-6.12/626-v6.16-net-bridge-locally-receive-all-multicast-packets-if-.patch


+ 4 - 4
target/linux/generic/pending-6.12/650-net-pppoe-implement-GRO-support.patch

@@ -23,7 +23,7 @@ Signed-off-by: Felix Fietkau <[email protected]>
  
  #include <linux/uaccess.h>
  
-@@ -435,7 +436,7 @@ static int pppoe_rcv(struct sk_buff *skb
+@@ -434,7 +435,7 @@ static int pppoe_rcv(struct sk_buff *skb
  	if (skb->len < len)
  		goto drop;
  
@@ -32,7 +32,7 @@ Signed-off-by: Felix Fietkau <[email protected]>
  		goto drop;
  
  	ph = pppoe_hdr(skb);
-@@ -1173,6 +1174,161 @@ static struct pernet_operations pppoe_ne
+@@ -1176,6 +1177,161 @@ static struct pernet_operations pppoe_ne
  	.size = sizeof(struct pppoe_net),
  };
  
@@ -194,7 +194,7 @@ Signed-off-by: Felix Fietkau <[email protected]>
  static int __init pppoe_init(void)
  {
  	int err;
-@@ -1189,6 +1345,7 @@ static int __init pppoe_init(void)
+@@ -1192,6 +1348,7 @@ static int __init pppoe_init(void)
  	if (err)
  		goto out_unregister_pppoe_proto;
  
@@ -202,7 +202,7 @@ Signed-off-by: Felix Fietkau <[email protected]>
  	dev_add_pack(&pppoes_ptype);
  	dev_add_pack(&pppoed_ptype);
  	register_netdevice_notifier(&pppoe_notifier);
-@@ -1208,6 +1365,7 @@ static void __exit pppoe_exit(void)
+@@ -1211,6 +1368,7 @@ static void __exit pppoe_exit(void)
  	unregister_netdevice_notifier(&pppoe_notifier);
  	dev_remove_pack(&pppoed_ptype);
  	dev_remove_pack(&pppoes_ptype);

+ 8 - 6
target/linux/ipq40xx/patches-6.12/999-atm-mpoa-intel-dsl-phy-support.patch

@@ -4,7 +4,7 @@ Subject: [PATCH] UGW_SW-29163: ATM oam support
 
 --- a/drivers/net/ppp/ppp_generic.c
 +++ b/drivers/net/ppp/ppp_generic.c
-@@ -2982,6 +2982,22 @@ char *ppp_dev_name(struct ppp_channel *c
+@@ -2985,6 +2985,24 @@ char *ppp_dev_name(struct ppp_channel *c
  	return name;
  }
  
@@ -17,17 +17,19 @@ Subject: [PATCH] UGW_SW-29163: ATM oam support
 +	struct net_device *dev = NULL;
 +
 +	if (pch) {
-+		read_lock_bh(&pch->upl);
-+		if (pch->ppp && pch->ppp->dev)
-+			dev = pch->ppp->dev;
-+		read_unlock_bh(&pch->upl);
++		struct ppp *ppp;
++		rcu_read_lock();
++		ppp = rcu_dereference(pch->ppp);
++		if (ppp)
++			dev = ppp->dev;
++		rcu_read_unlock();
 +	}
 +	return dev;
 +}
  
  /*
   * Disconnect a channel from the generic layer.
-@@ -3633,6 +3649,7 @@ EXPORT_SYMBOL(ppp_unregister_channel);
+@@ -3646,6 +3664,7 @@ EXPORT_SYMBOL(ppp_unregister_channel);
  EXPORT_SYMBOL(ppp_channel_index);
  EXPORT_SYMBOL(ppp_unit_number);
  EXPORT_SYMBOL(ppp_dev_name);