summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSerge Semin <fancer.lancer@gmail.com>2025-06-04 21:44:37 +0300
committerSerge Semin <fancer.lancer@gmail.com>2026-08-13 20:31:13 +0300
commit2a2b01159b6610a1bf7137f8700921ec46efd846 (patch)
treee9c7881a9e0f0359ff99bb8c79eb4807e3986248
parent6f7c7e07cd279f8d4d1eb3c887b6477df1273c09 (diff)
downloadlinux-2a2b01159b6610a1bf7137f8700921ec46efd846.tar.gz
linux-2a2b01159b6610a1bf7137f8700921ec46efd846.zip
net: stmmac: Fix incorrect IP-csum insertion algo
Currently the IP checksum insertion on Tx is implied to be performed by HW for any IPv4/IPv6 network protocol types. It's definitely incorrect since based on [1,2,3] Tx COE supports the TCP/UDP/ICMP payloads only. Moreover the IPv6 extension headers are limited to be a single Hop-by-Hop and multiple Destination headers. From that perspective the commit c2945c435c99 ("net: stmmac: Prevent DSA tags from breaking COE") wasn't fully correct in providing just IP-type constraint. Let's fix the problem by providing a comprehensive method which would make sure that the passed frame can be checksummed by the controller Tx Checksum Offload Engine. [1] DesignWare Cores Ethernet MAC Universal Databook, Revision 3.73a, October 2013, p. 187. [2] DesignWare Cores Ethernet Quality-of-Service Databook, Revision 5.20a, April 2020, pp. 366. [3] DesignWare Cores XGMAC - 10G Ethernet MAC Databook, Revision 3.20a, September 2022, p.251. Fixes: c2945c435c99 ("net: stmmac: Prevent DSA tags from breaking COE") Signed-off-by: Serge Semin <fancer.lancer@gmail.com>
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/stmmac_main.c137
1 files changed, 95 insertions, 42 deletions
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
index d74927fbf148..f0d395b86319 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
@@ -18,6 +18,7 @@
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/ip.h>
+#include <linux/ipv6.h>
#include <linux/tcp.h>
#include <linux/skbuff.h>
#include <linux/ethtool.h>
@@ -41,6 +42,7 @@
#include <linux/udp.h>
#include <linux/bpf_trace.h>
#include <net/devlink.h>
+#include <net/ipv6.h>
#include <net/page_pool/helpers.h>
#include <net/pkt_cls.h>
#include <net/xdp_sock_drv.h>
@@ -4309,6 +4311,91 @@ static bool stmmac_vlan_insert(struct stmmac_priv *priv, struct sk_buff *skb,
}
/**
+ * stmmac_csum_insert - Insert CSUM into the frame
+ * @priv: driver private structure
+ * @skb : the socket buffer
+ * @queue: TX queue index
+ * Description :
+ * This function will either insert all the uninitialized checksum into the
+ * frame or return a request for the Tx COE to do that if it's supported.
+ *
+ * Return: zero if CSUM was inserted into the frame, one if Tx COE is supposed
+ * to do that, negative error otherwise.
+ */
+static int stmmac_csum_insert(struct stmmac_priv *priv, struct sk_buff *skb,
+ int queue)
+{
+ struct ipv6_opt_hdr *hp;
+ __be16 l3proto;
+ int depth = 0;
+ u8 l4proto;
+
+ /* No checksum required */
+ if (!(skb->ip_summed == CHECKSUM_PARTIAL))
+ return 0;
+
+ /* Tx COE might be available on a limited set of the queues */
+ if (priv->plat->tx_queues_cfg[queue].coe_unsupported)
+ goto sw_checksum;
+
+ /* Tx COE supports Ethernet II frames only (EtherType >= 0x0600) */
+ l3proto = eth_header_parse_protocol(skb);
+ if (!eth_proto_is_802_3(l3proto))
+ goto sw_checksum;
+
+ /* Tx COE supports C-/S-VLAN tagged IP-packets (excl. hwaccel tag?) */
+ l3proto = __vlan_get_protocol(skb, l3proto, &depth);
+ if (depth > (ETH_HLEN + VLAN_HLEN))
+ goto sw_checksum;
+
+ /* Tx COE supports IPv4/IPv6 types with TCP/UDP/ICMP protocols */
+ if ((priv->dev->features & NETIF_F_IP_CSUM) &&
+ (l3proto == htons(ETH_P_IP))) {
+ l4proto = ip_hdr(skb)->protocol;
+
+ switch(l4proto) {
+ case IPPROTO_ICMP:
+ case IPPROTO_TCP:
+ case IPPROTO_UDP:
+ return 1;
+ default:
+ goto sw_checksum;
+ }
+ } else if ((priv->dev->features & NETIF_F_IPV6_CSUM) &&
+ (l3proto == htons(ETH_P_IPV6))) {
+ l4proto = ipv6_hdr(skb)->nexthdr;
+ depth = sizeof(struct ipv6hdr);
+
+ /* Tx COE supports single Hop-by-Hop and multiple Dest headers */
+ do {
+ switch(l4proto) {
+ case NEXTHDR_TCP:
+ case NEXTHDR_UDP:
+ case NEXTHDR_ICMP:
+ return 1;
+ case NEXTHDR_HOP:
+ if (depth != sizeof(struct ipv6hdr))
+ goto sw_checksum;
+ break;
+ case NEXTHDR_DEST:
+ break;
+ default:
+ goto sw_checksum;
+ }
+
+ hp = (struct ipv6_opt_hdr *)(skb_network_header(skb) +
+ depth);
+ l4proto = hp->nexthdr;
+ depth += ipv6_optlen(hp);
+ } while (l4proto != NEXTHDR_NONE);
+ }
+
+sw_checksum:
+ return unlikely(skb_csum_is_sctp(skb)) ?
+ skb_crc32c_csum_help(skb) : skb_checksum_help(skb);
+}
+
+/**
* stmmac_tso_allocator - close entry point of the driver
* @priv: driver private structure
* @des: buffer start address
@@ -4663,28 +4750,6 @@ flush_ring:
}
/**
- * stmmac_has_ip_ethertype() - Check if packet has IP ethertype
- * @skb: socket buffer to check
- *
- * Check if a packet has an ethertype that will trigger the IP header checks
- * and IP/TCP checksum engine of the stmmac core.
- *
- * Return: true if the ethertype can trigger the checksum engine, false
- * otherwise
- */
-static bool stmmac_has_ip_ethertype(struct sk_buff *skb)
-{
- int depth = 0;
- __be16 proto;
-
- proto = __vlan_get_protocol(skb, eth_header_parse_protocol(skb),
- &depth);
-
- return (depth <= ETH_HLEN) &&
- (proto == htons(ETH_P_IP) || proto == htons(ETH_P_IPV6));
-}
-
-/**
* stmmac_xmit - Tx entry point of the driver
* @skb : the socket buffer
* @dev : device pointer
@@ -4755,22 +4820,11 @@ static netdev_tx_t stmmac_xmit(struct sk_buff *skb, struct net_device *dev)
first_entry = entry;
WARN_ON(tx_q->tx_skbuff[first_entry]);
- csum_insertion = (skb->ip_summed == CHECKSUM_PARTIAL);
- /* DWMAC IPs can be synthesized to support tx coe only for a few tx
- * queues. In that case, checksum offloading for those queues that don't
- * support tx coe needs to fallback to software checksum calculation.
- *
- * Packets that won't trigger the COE e.g. most DSA-tagged packets will
- * also have to be checksummed in software.
- */
- if (csum_insertion &&
- (priv->plat->tx_queues_cfg[queue].coe_unsupported ||
- !stmmac_has_ip_ethertype(skb))) {
- if (unlikely(skb_checksum_help(skb))) {
- drop = true;
- goto flush_ring;
- }
- csum_insertion = !csum_insertion;
+ /* Check if CSUM should be inserted by HW */
+ csum_insertion = stmmac_csum_insert(priv, skb, queue);
+ if (csum_insertion < 0) {
+ drop = true;
+ goto flush_ring;
}
if (likely(priv->extend_desc))
@@ -6178,9 +6232,6 @@ static netdev_features_t stmmac_fix_features(struct net_device *dev,
{
struct stmmac_priv *priv = netdev_priv(dev);
- if (!priv->plat->tx_coe)
- features &= ~NETIF_F_CSUM_MASK;
-
/* Some GMAC devices have a bugged Jumbo frame support that
* needs to have the Tx COE disabled for oversized frames
* (due to limited buffer sizes). In this case we disable
@@ -8258,10 +8309,12 @@ static int __stmmac_dvr_probe(struct device *device,
ndev->xdp_metadata_ops = &stmmac_xdp_metadata_ops;
ndev->xsk_tx_metadata_ops = &stmmac_xsk_tx_metadata_ops;
- ndev->hw_features = NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM;
ndev->xdp_features = NETDEV_XDP_ACT_BASIC | NETDEV_XDP_ACT_REDIRECT |
NETDEV_XDP_ACT_XSK_ZEROCOPY;
+ if (priv->plat->tx_coe)
+ ndev->hw_features |= NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM;
+
if (priv->plat->rx_coe)
ndev->hw_features |= NETIF_F_RXCSUM;