--- /dev/null
+From 1a3e9b7a6b09e8ab3d2af019e4a392622685855e Mon Sep 17 00:00:00 2001
+From: Qingfang Deng <dqfext@gmail.com>
+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 <dqfext@gmail.com>
+Link: https://patch.msgid.link/20250610083211.909015-1-dqfext@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ 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;
--- /dev/null
+From b8844aab519a154808dbce15a132f3e8f1c34af6 Mon Sep 17 00:00:00 2001
+From: Qingfang Deng <dqfext@gmail.com>
+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 <dqfext@gmail.com>
+Link: https://patch.msgid.link/20250822012548.6232-1-dqfext@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ 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);
--- /dev/null
+From 72cdc67e7fa74931b055df3a76852bab551f1a04 Mon Sep 17 00:00:00 2001
+From: Qingfang Deng <dqfext@gmail.com>
+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 <dqfext@gmail.com>
+Reviewed-by: Eric Dumazet <edumazet@google.com>
+Link: https://patch.msgid.link/20250828012018.15922-1-dqfext@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ 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;
--- /dev/null
+From 4f54dff818d7b5b1d84becd5d90bc46e6233c0d7 Mon Sep 17 00:00:00 2001
+From: Qingfang Deng <dqfext@gmail.com>
+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 <dqfext@gmail.com>
+Reviewed-by: Eric Dumazet <edumazet@google.com>
+Link: https://patch.msgid.link/20250828012018.15922-2-dqfext@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ 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;
--- /dev/null
+From 42fcb213e58a7da33d5d2d7517b4e521025c68c3 Mon Sep 17 00:00:00 2001
+From: Qingfang Deng <dqfext@gmail.com>
+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 <dqfext@gmail.com>
+Link: https://patch.msgid.link/20260129012902.941-1-dqfext@gmail.com
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ 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;
#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;
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),
};
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;
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);
--- 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;
}
+ 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);