]> git.ipfire.org Git - thirdparty/openwrt.git/commitdiff
kernel: backport pppoe improvements 21892/head
authorQingfang Deng <dqfext@gmail.com>
Fri, 6 Feb 2026 09:38:50 +0000 (17:38 +0800)
committerHauke Mehrtens <hauke@hauke-m.de>
Tue, 10 Feb 2026 23:05:55 +0000 (00:05 +0100)
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 <dqfext@gmail.com>
Link: https://github.com/openwrt/openwrt/pull/21892
Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
target/linux/generic/backport-6.12/621-v6.17-ppp-convert-to-percpu-netstats.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/622-v6.18-ppp-remove-rwlock-usage.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/623-v6.18-pppoe-remove-rwlock-usage.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/624-v6.18-pppoe-drop-sock-reference-counting-on-fast-path.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/625-v7.0-ppp-enable-TX-scatter-gather.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-v6.16-net-bridge-locally-receive-all-multicast-packets-if-.patch [moved from target/linux/generic/backport-6.12/625-v6.16-net-bridge-locally-receive-all-multicast-packets-if-.patch with 100% similarity]
target/linux/generic/pending-6.12/650-net-pppoe-implement-GRO-support.patch
target/linux/ipq40xx/patches-6.12/999-atm-mpoa-intel-dsl-phy-support.patch

diff --git a/target/linux/generic/backport-6.12/621-v6.17-ppp-convert-to-percpu-netstats.patch b/target/linux/generic/backport-6.12/621-v6.17-ppp-convert-to-percpu-netstats.patch
new file mode 100644 (file)
index 0000000..874d2bf
--- /dev/null
@@ -0,0 +1,126 @@
+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;
diff --git a/target/linux/generic/backport-6.12/622-v6.18-ppp-remove-rwlock-usage.patch b/target/linux/generic/backport-6.12/622-v6.18-ppp-remove-rwlock-usage.patch
new file mode 100644 (file)
index 0000000..f66a522
--- /dev/null
@@ -0,0 +1,334 @@
+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);
diff --git a/target/linux/generic/backport-6.12/623-v6.18-pppoe-remove-rwlock-usage.patch b/target/linux/generic/backport-6.12/623-v6.18-pppoe-remove-rwlock-usage.patch
new file mode 100644 (file)
index 0000000..619f1cb
--- /dev/null
@@ -0,0 +1,321 @@
+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;
diff --git a/target/linux/generic/backport-6.12/624-v6.18-pppoe-drop-sock-reference-counting-on-fast-path.patch b/target/linux/generic/backport-6.12/624-v6.18-pppoe-drop-sock-reference-counting-on-fast-path.patch
new file mode 100644 (file)
index 0000000..2637621
--- /dev/null
@@ -0,0 +1,124 @@
+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;
diff --git a/target/linux/generic/backport-6.12/625-v7.0-ppp-enable-TX-scatter-gather.patch b/target/linux/generic/backport-6.12/625-v7.0-ppp-enable-TX-scatter-gather.patch
new file mode 100644 (file)
index 0000000..5ea56f5
--- /dev/null
@@ -0,0 +1,104 @@
+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;
index 11d0f03fbaa9eb097b93b66d319c043b0607ffa9..2aa55f84b9987c3e8bf76f4f29730e4d3ed999dc 100644 (file)
@@ -23,7 +23,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
  
  #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 <nbd@nbd.name>
                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 <nbd@nbd.name>
  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 <nbd@nbd.name>
        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);
index d38070b13e1a99cee282419540f8e43bc2202c3e..942292e8a1f43f71b8228ba8a3b48db35380e76b 100644 (file)
@@ -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);