diff options
| author | Serge Semin <fancer.lancer@gmail.com> | 2025-06-04 21:44:37 +0300 |
|---|---|---|
| committer | Serge Semin <fancer.lancer@gmail.com> | 2026-08-13 20:31:13 +0300 |
| commit | 2a2b01159b6610a1bf7137f8700921ec46efd846 (patch) | |
| tree | e9c7881a9e0f0359ff99bb8c79eb4807e3986248 | |
| parent | 6f7c7e07cd279f8d4d1eb3c887b6477df1273c09 (diff) | |
| download | linux-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.c | 137 |
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; |
