gemini: In-flight ethernet patches
authorLinus Walleij <linus.walleij@linaro.org>
Wed, 15 May 2024 08:21:47 +0000 (10:21 +0200)
committerLinus Walleij <linus.walleij@linaro.org>
Sun, 19 May 2024 20:06:11 +0000 (22:06 +0200)
These patches have partial acceptance upstream and are still
a WIP, now there is merge window for kernel v6.10 so these
will not be reposted until that is over. In the meantime,
let's add the current state to OpenWrt so the ethernet on
Gemini is up and working (tested on several devices).

Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
target/linux/gemini/patches-6.6/0003-net-ethernet-cortina-Locking-fixes.patch [new file with mode: 0644]
target/linux/gemini/patches-6.6/0004-net-ethernet-cortina-Restore-TSO-support.patch [new file with mode: 0644]
target/linux/gemini/patches-6.6/0005-net-ethernet-cortina-Use-TSO-also-on-common-TCP.patch [new file with mode: 0644]
target/linux/gemini/patches-6.6/0006-net-ethernet-cortina-Rename-adjust-link-callback.patch [new file with mode: 0644]
target/linux/gemini/patches-6.6/0007-net-ethernet-cortina-Use-negotiated-TX-RX-pause.patch [new file with mode: 0644]
target/linux/gemini/patches-6.6/0008-net-ethernet-cortina-Implement-.set_pauseparam.patch [new file with mode: 0644]

diff --git a/target/linux/gemini/patches-6.6/0003-net-ethernet-cortina-Locking-fixes.patch b/target/linux/gemini/patches-6.6/0003-net-ethernet-cortina-Locking-fixes.patch
new file mode 100644 (file)
index 0000000..661e928
--- /dev/null
@@ -0,0 +1,73 @@
+From 81889eb2b37bc21df4ff259441e8fc12d4f27cd9 Mon Sep 17 00:00:00 2001
+From: Linus Walleij <linus.walleij@linaro.org>
+Date: Thu, 9 May 2024 08:48:31 +0200
+Subject: [PATCH] net: ethernet: cortina: Locking fixes
+
+This fixes a probably long standing problem in the Cortina
+Gemini ethernet driver: there are some paths in the code
+where the IRQ registers are written without taking the proper
+locks.
+
+Fixes: 4d5ae32f5e1e ("net: ethernet: Add a driver for Gemini gigabit ethernet")
+Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
+---
+ drivers/net/ethernet/cortina/gemini.c | 12 ++++++++++--
+ 1 file changed, 10 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/ethernet/cortina/gemini.c
++++ b/drivers/net/ethernet/cortina/gemini.c
+@@ -1107,10 +1107,13 @@ static void gmac_tx_irq_enable(struct ne
+ {
+       struct gemini_ethernet_port *port = netdev_priv(netdev);
+       struct gemini_ethernet *geth = port->geth;
++      unsigned long flags;
+       u32 val, mask;
+       netdev_dbg(netdev, "%s device %d\n", __func__, netdev->dev_id);
++      spin_lock_irqsave(&geth->irq_lock, flags);
++
+       mask = GMAC0_IRQ0_TXQ0_INTS << (6 * netdev->dev_id + txq);
+       if (en)
+@@ -1119,6 +1122,8 @@ static void gmac_tx_irq_enable(struct ne
+       val = readl(geth->base + GLOBAL_INTERRUPT_ENABLE_0_REG);
+       val = en ? val | mask : val & ~mask;
+       writel(val, geth->base + GLOBAL_INTERRUPT_ENABLE_0_REG);
++
++      spin_unlock_irqrestore(&geth->irq_lock, flags);
+ }
+ static void gmac_tx_irq(struct net_device *netdev, unsigned int txq_num)
+@@ -1415,15 +1420,19 @@ static unsigned int gmac_rx(struct net_d
+       union gmac_rxdesc_3 word3;
+       struct page *page = NULL;
+       unsigned int page_offs;
++      unsigned long flags;
+       unsigned short r, w;
+       union dma_rwptr rw;
+       dma_addr_t mapping;
+       int frag_nr = 0;
++      spin_lock_irqsave(&geth->irq_lock, flags);
+       rw.bits32 = readl(ptr_reg);
+       /* Reset interrupt as all packages until here are taken into account */
+       writel(DEFAULT_Q0_INT_BIT << netdev->dev_id,
+              geth->base + GLOBAL_INTERRUPT_STATUS_1_REG);
++      spin_unlock_irqrestore(&geth->irq_lock, flags);
++
+       r = rw.bits.rptr;
+       w = rw.bits.wptr;
+@@ -1726,10 +1735,9 @@ static irqreturn_t gmac_irq(int irq, voi
+               gmac_update_hw_stats(netdev);
+       if (val & (GMAC0_RX_OVERRUN_INT_BIT << (netdev->dev_id * 8))) {
++              spin_lock(&geth->irq_lock);
+               writel(GMAC0_RXDERR_INT_BIT << (netdev->dev_id * 8),
+                      geth->base + GLOBAL_INTERRUPT_STATUS_4_REG);
+-
+-              spin_lock(&geth->irq_lock);
+               u64_stats_update_begin(&port->ir_stats_syncp);
+               ++port->stats.rx_fifo_errors;
+               u64_stats_update_end(&port->ir_stats_syncp);
diff --git a/target/linux/gemini/patches-6.6/0004-net-ethernet-cortina-Restore-TSO-support.patch b/target/linux/gemini/patches-6.6/0004-net-ethernet-cortina-Restore-TSO-support.patch
new file mode 100644 (file)
index 0000000..809941a
--- /dev/null
@@ -0,0 +1,124 @@
+From 30fcba19ed88997a2909e4a68b4d39ff371357c3 Mon Sep 17 00:00:00 2001
+From: Linus Walleij <linus.walleij@linaro.org>
+Date: Wed, 1 May 2024 21:46:31 +0200
+Subject: [PATCH 1/5] net: ethernet: cortina: Restore TSO support
+
+An earlier commit deleted the TSO support in the Cortina Gemini
+driver because the driver was confusing gso_size and MTU,
+probably because what the Linux kernel calls "gso_size" was
+called "MTU" in the datasheet.
+
+Restore the functionality properly reading the gso_size from
+the skbuff.
+
+Tested with iperf3, running a server on a different machine
+and client on the device with the cortina gemini ethernet:
+
+Connecting to host 192.168.1.2, port 5201
+60008000.ethernet-port eth0: segment offloading mss = 05ea len=1c8a
+60008000.ethernet-port eth0: segment offloading mss = 05ea len=1c8a
+60008000.ethernet-port eth0: segment offloading mss = 05ea len=27da
+60008000.ethernet-port eth0: segment offloading mss = 05ea len=0b92
+60008000.ethernet-port eth0: segment offloading mss = 05ea len=2bda
+(...)
+
+(The hardware MSS 0x05ea here includes the ethernet headers.)
+
+If I disable all segment offloading on the receiving host and
+dump packets using tcpdump -xx like this:
+
+ethtool -K enp2s0 gro off gso off tso off
+tcpdump -xx -i enp2s0 host 192.168.1.136
+
+I get segmented packages such as this when running iperf3:
+
+23:16:54.024139 IP OpenWrt.lan.59168 > Fecusia.targus-getdata1:
+Flags [.], seq 1486:2934, ack 1, win 4198,
+options [nop,nop,TS val 3886192908 ecr 3601341877], length 1448
+0x0000:  fc34 9701 a0c6 14d6 4da8 3c4f 0800 4500
+0x0010:  05dc 16a0 4000 4006 9aa1 c0a8 0188 c0a8
+0x0020:  0102 e720 1451 ff25 9822 4c52 29cf 8010
+0x0030:  1066 ac8c 0000 0101 080a e7a2 990c d6a8
+(...)
+0x05c0:  5e49 e109 fe8c 4617 5e18 7a82 7eae d647
+0x05d0:  e8ee ae64 dc88 c897 3f8a 07a4 3a33 6b1b
+0x05e0:  3501 a30f 2758 cc44 4b4a
+
+Several such packets often follow after each other verifying
+the segmentation into 0x05a8 (1448) byte packages also on the
+reveiving end. As can be seen, the ethernet frames are
+0x05ea (1514) in size.
+
+Performance with iperf3 before this patch: ~15.5 Mbit/s
+Performance with iperf3 after this patch: ~175 Mbit/s
+
+This was running a 60 second test (twice) the best measurement
+was 179 Mbit/s.
+
+For comparison if I run iperf3 with UDP I get around 1.05 Mbit/s
+both before and after this patch.
+
+While this is a gigabit ethernet interface, the CPU is a cheap
+D-Link DIR-685 router (based on the ARMv5 Faraday FA526 at
+~50 MHz), and the software is not supposed to drive traffic,
+as the device has a DSA chip, so this kind of numbers can be
+expected.
+
+Fixes: ac631873c9e7 ("net: ethernet: cortina: Drop TSO support")
+Reviewed-by: Eric Dumazet <edumazet@google.com>
+Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
+---
+ drivers/net/ethernet/cortina/gemini.c | 23 +++++++++++++++++++----
+ 1 file changed, 19 insertions(+), 4 deletions(-)
+
+--- a/drivers/net/ethernet/cortina/gemini.c
++++ b/drivers/net/ethernet/cortina/gemini.c
+@@ -79,7 +79,8 @@ MODULE_PARM_DESC(debug, "Debug level (0=
+ #define GMAC0_IRQ4_8 (GMAC0_MIB_INT_BIT | GMAC0_RX_OVERRUN_INT_BIT)
+ #define GMAC_OFFLOAD_FEATURES (NETIF_F_SG | NETIF_F_IP_CSUM | \
+-                             NETIF_F_IPV6_CSUM | NETIF_F_RXCSUM)
++                             NETIF_F_IPV6_CSUM | NETIF_F_RXCSUM | \
++                             NETIF_F_TSO | NETIF_F_TSO_ECN | NETIF_F_TSO6)
+ /**
+  * struct gmac_queue_page - page buffer per-page info
+@@ -1148,13 +1149,25 @@ static int gmac_map_tx_bufs(struct net_d
+       skb_frag_t *skb_frag;
+       dma_addr_t mapping;
+       void *buffer;
++      u16 mss;
+       int ret;
+-      /* TODO: implement proper TSO using MTU in word3 */
+       word1 = skb->len;
+       word3 = SOF_BIT;
+-      if (skb->len >= ETH_FRAME_LEN) {
++      mss = skb_shinfo(skb)->gso_size;
++      if (mss) {
++              /* This means we are dealing with TCP and skb->len is the
++               * sum total of all the segments. The TSO will deal with
++               * chopping this up for us.
++               */
++              /* The accelerator needs the full frame size here */
++              mss += skb_tcp_all_headers(skb);
++              netdev_dbg(netdev, "segment offloading mss = %04x len=%04x\n",
++                         mss, skb->len);
++              word1 |= TSS_MTU_ENABLE_BIT;
++              word3 |= mss;
++      } else if (skb->len >= ETH_FRAME_LEN) {
+               /* Hardware offloaded checksumming isn't working on frames
+                * bigger than 1514 bytes. A hypothesis about this is that the
+                * checksum buffer is only 1518 bytes, so when the frames get
+@@ -1169,7 +1182,9 @@ static int gmac_map_tx_bufs(struct net_d
+                               return ret;
+               }
+               word1 |= TSS_BYPASS_BIT;
+-      } else if (skb->ip_summed == CHECKSUM_PARTIAL) {
++      }
++
++      if (skb->ip_summed == CHECKSUM_PARTIAL) {
+               int tcp = 0;
+               /* We do not switch off the checksumming on non TCP/UDP
diff --git a/target/linux/gemini/patches-6.6/0005-net-ethernet-cortina-Use-TSO-also-on-common-TCP.patch b/target/linux/gemini/patches-6.6/0005-net-ethernet-cortina-Use-TSO-also-on-common-TCP.patch
new file mode 100644 (file)
index 0000000..c690b8f
--- /dev/null
@@ -0,0 +1,95 @@
+From 91fb8a7328dda827bc6c0da240a1eb17028416cd Mon Sep 17 00:00:00 2001
+From: Linus Walleij <linus.walleij@linaro.org>
+Date: Thu, 9 May 2024 23:59:28 +0200
+Subject: [PATCH 2/5] net: ethernet: cortina: Use TSO also on common TCP
+
+It is possible to push the segment offloader to also
+process non-segmented frames: just pass the skb->len
+or desired MSS to the offloader and it will handle them.
+
+This is especially good if the user sets up the MTU
+and the frames get big, because the checksumming engine
+cannot handle any frames bigger than 1518 bytes, so
+segmenting them all to be at max that will be helpful
+for the hardware, which only need to quirk odd frames
+such as big UDP ping packets.
+
+The vendor driver always uses the TSO like this, and
+the driver seems more stable after this, so apparently
+the hardware may have been engineered to always use
+the TSO on anything it can handle.
+
+Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
+---
+ drivers/net/ethernet/cortina/gemini.c | 31 +++++++++++++++++++++------
+ 1 file changed, 24 insertions(+), 7 deletions(-)
+
+--- a/drivers/net/ethernet/cortina/gemini.c
++++ b/drivers/net/ethernet/cortina/gemini.c
+@@ -1148,6 +1148,7 @@ static int gmac_map_tx_bufs(struct net_d
+       struct gmac_txdesc *txd;
+       skb_frag_t *skb_frag;
+       dma_addr_t mapping;
++      bool tcp = false;
+       void *buffer;
+       u16 mss;
+       int ret;
+@@ -1155,6 +1156,13 @@ static int gmac_map_tx_bufs(struct net_d
+       word1 = skb->len;
+       word3 = SOF_BIT;
++      /* Determine if we are doing TCP */
++      if (skb->protocol == htons(ETH_P_IP))
++              tcp = (ip_hdr(skb)->protocol == IPPROTO_TCP);
++      else
++              /* IPv6 */
++              tcp = (ipv6_hdr(skb)->nexthdr == IPPROTO_TCP);
++
+       mss = skb_shinfo(skb)->gso_size;
+       if (mss) {
+               /* This means we are dealing with TCP and skb->len is the
+@@ -1167,6 +1175,20 @@ static int gmac_map_tx_bufs(struct net_d
+                          mss, skb->len);
+               word1 |= TSS_MTU_ENABLE_BIT;
+               word3 |= mss;
++      } else if (tcp) {
++              /* Even if we are not using TSO, use the segment offloader
++               * for transferring the TCP frame: the TSO engine will deal
++               * with chopping up frames that exceed ETH_DATA_LEN which
++               * the checksumming engine cannot handle (see below) into
++               * manageable chunks. It flawlessly deals with quite big
++               * frames and frames containing custom DSA EtherTypes.
++               */
++              mss = netdev->mtu + skb_tcp_all_headers(skb);
++              mss = min(mss, skb->len);
++              netdev_dbg(netdev, "botched TSO len %04x mtu %04x mss %04x\n",
++                         skb->len, netdev->mtu, mss);
++              word1 |= TSS_MTU_ENABLE_BIT;
++              word3 |= mss;
+       } else if (skb->len >= ETH_FRAME_LEN) {
+               /* Hardware offloaded checksumming isn't working on frames
+                * bigger than 1514 bytes. A hypothesis about this is that the
+@@ -1185,21 +1207,16 @@ static int gmac_map_tx_bufs(struct net_d
+       }
+       if (skb->ip_summed == CHECKSUM_PARTIAL) {
+-              int tcp = 0;
+-
+               /* We do not switch off the checksumming on non TCP/UDP
+                * frames: as is shown from tests, the checksumming engine
+                * is smart enough to see that a frame is not actually TCP
+                * or UDP and then just pass it through without any changes
+                * to the frame.
+                */
+-              if (skb->protocol == htons(ETH_P_IP)) {
++              if (skb->protocol == htons(ETH_P_IP))
+                       word1 |= TSS_IP_CHKSUM_BIT;
+-                      tcp = ip_hdr(skb)->protocol == IPPROTO_TCP;
+-              } else { /* IPv6 */
++              else
+                       word1 |= TSS_IPV6_ENABLE_BIT;
+-                      tcp = ipv6_hdr(skb)->nexthdr == IPPROTO_TCP;
+-              }
+               word1 |= tcp ? TSS_TCP_CHKSUM_BIT : TSS_UDP_CHKSUM_BIT;
+       }
diff --git a/target/linux/gemini/patches-6.6/0006-net-ethernet-cortina-Rename-adjust-link-callback.patch b/target/linux/gemini/patches-6.6/0006-net-ethernet-cortina-Rename-adjust-link-callback.patch
new file mode 100644 (file)
index 0000000..bbdef8f
--- /dev/null
@@ -0,0 +1,36 @@
+From fa01c904b844e6033445f75b0b4d46a8e83b6086 Mon Sep 17 00:00:00 2001
+From: Linus Walleij <linus.walleij@linaro.org>
+Date: Fri, 10 May 2024 19:48:27 +0200
+Subject: [PATCH 3/5] net: ethernet: cortina: Rename adjust link callback
+
+The callback passed to of_phy_get_and_connect() in the
+Cortina Gemini driver is called "gmac_speed_set" which is
+archaic, rename it to "gmac_adjust_link" following the
+pattern of most other drivers.
+
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
+---
+ drivers/net/ethernet/cortina/gemini.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/ethernet/cortina/gemini.c
++++ b/drivers/net/ethernet/cortina/gemini.c
+@@ -288,7 +288,7 @@ static void gmac_set_flow_control(struct
+       spin_unlock_irqrestore(&port->config_lock, flags);
+ }
+-static void gmac_speed_set(struct net_device *netdev)
++static void gmac_adjust_link(struct net_device *netdev)
+ {
+       struct gemini_ethernet_port *port = netdev_priv(netdev);
+       struct phy_device *phydev = netdev->phydev;
+@@ -367,7 +367,7 @@ static int gmac_setup_phy(struct net_dev
+       phy = of_phy_get_and_connect(netdev,
+                                    dev->of_node,
+-                                   gmac_speed_set);
++                                   gmac_adjust_link);
+       if (!phy)
+               return -ENODEV;
+       netdev->phydev = phy;
diff --git a/target/linux/gemini/patches-6.6/0007-net-ethernet-cortina-Use-negotiated-TX-RX-pause.patch b/target/linux/gemini/patches-6.6/0007-net-ethernet-cortina-Use-negotiated-TX-RX-pause.patch
new file mode 100644 (file)
index 0000000..a1b8707
--- /dev/null
@@ -0,0 +1,46 @@
+From 50ac9765c674bac803719c6b8294670edc6df31d Mon Sep 17 00:00:00 2001
+From: Linus Walleij <linus.walleij@linaro.org>
+Date: Fri, 10 May 2024 19:44:39 +0200
+Subject: [PATCH 4/5] net: ethernet: cortina: Use negotiated TX/RX pause
+
+Instead of directly poking into registers of the PHY, use
+the existing function to query phylib about this directly.
+
+Suggested-by: Andrew Lunn <andrew@lunn.ch>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
+---
+ drivers/net/ethernet/cortina/gemini.c | 15 +++++----------
+ 1 file changed, 5 insertions(+), 10 deletions(-)
+
+--- a/drivers/net/ethernet/cortina/gemini.c
++++ b/drivers/net/ethernet/cortina/gemini.c
+@@ -293,8 +293,8 @@ static void gmac_adjust_link(struct net_
+       struct gemini_ethernet_port *port = netdev_priv(netdev);
+       struct phy_device *phydev = netdev->phydev;
+       union gmac_status status, old_status;
+-      int pause_tx = 0;
+-      int pause_rx = 0;
++      bool pause_tx = false;
++      bool pause_rx = false;
+       status.bits32 = readl(port->gmac_base + GMAC_STATUS);
+       old_status.bits32 = status.bits32;
+@@ -329,14 +329,9 @@ static void gmac_adjust_link(struct net_
+       }
+       if (phydev->duplex == DUPLEX_FULL) {
+-              u16 lcladv = phy_read(phydev, MII_ADVERTISE);
+-              u16 rmtadv = phy_read(phydev, MII_LPA);
+-              u8 cap = mii_resolve_flowctrl_fdx(lcladv, rmtadv);
+-
+-              if (cap & FLOW_CTRL_RX)
+-                      pause_rx = 1;
+-              if (cap & FLOW_CTRL_TX)
+-                      pause_tx = 1;
++              phy_get_pause(phydev, &pause_tx, &pause_rx);
++              netdev_dbg(netdev, "set negotiated pause params pause TX = %s, pause RX = %s\n",
++                         pause_tx ? "ON" : "OFF", pause_rx ? "ON" : "OFF");
+       }
+       gmac_set_flow_control(netdev, pause_tx, pause_rx);
diff --git a/target/linux/gemini/patches-6.6/0008-net-ethernet-cortina-Implement-.set_pauseparam.patch b/target/linux/gemini/patches-6.6/0008-net-ethernet-cortina-Implement-.set_pauseparam.patch
new file mode 100644 (file)
index 0000000..ad7594e
--- /dev/null
@@ -0,0 +1,46 @@
+From 4eed4b87f17d10b7586349c13c3a30f9c24c9ba4 Mon Sep 17 00:00:00 2001
+From: Linus Walleij <linus.walleij@linaro.org>
+Date: Wed, 8 May 2024 23:21:17 +0200
+Subject: [PATCH 5/5] net: ethernet: cortina: Implement .set_pauseparam()
+
+The Cortina Gemini ethernet can very well set up TX or RX
+pausing, so add this functionality to the driver in a
+.set_pauseparam() callback. Essentially just call down to
+phylib and let phylib deal with this, .adjust_link()
+will respect the setting from phylib.
+
+Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
+---
+ drivers/net/ethernet/cortina/gemini.c | 14 ++++++++++++++
+ 1 file changed, 14 insertions(+)
+
+--- a/drivers/net/ethernet/cortina/gemini.c
++++ b/drivers/net/ethernet/cortina/gemini.c
+@@ -2143,6 +2143,19 @@ static void gmac_get_pauseparam(struct n
+       pparam->autoneg = true;
+ }
++static int gmac_set_pauseparam(struct net_device *netdev,
++                             struct ethtool_pauseparam *pparam)
++{
++      struct phy_device *phydev = netdev->phydev;
++
++      if (!pparam->autoneg)
++              return -EOPNOTSUPP;
++
++      phy_set_asym_pause(phydev, pparam->rx_pause, pparam->tx_pause);
++
++      return 0;
++}
++
+ static void gmac_get_ringparam(struct net_device *netdev,
+                              struct ethtool_ringparam *rp,
+                              struct kernel_ethtool_ringparam *kernel_rp,
+@@ -2263,6 +2276,7 @@ static const struct ethtool_ops gmac_351
+       .set_link_ksettings = gmac_set_ksettings,
+       .nway_reset     = gmac_nway_reset,
+       .get_pauseparam = gmac_get_pauseparam,
++      .set_pauseparam = gmac_set_pauseparam,
+       .get_ringparam  = gmac_get_ringparam,
+       .set_ringparam  = gmac_set_ringparam,
+       .get_coalesce   = gmac_get_coalesce,