Thread (27 messages) flat view 27 messages, 2 authors, 18h ago

Re: [PATCH net-next 03/15] ice: use NETIF_F_HW_CSUM instead of IP/IPV6

From: Jakub Kicinski <kuba@kernel.org>
Date: 2026-08-11 00:35:16

This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
ice: use NETIF_F_HW_CSUM instead of IP/IPV6

This switches the ice netdev checksum offload advertisement from
NETIF_F_IP_CSUM/NETIF_F_IPV6_CSUM to NETIF_F_HW_CSUM, so IPv6 packets
carrying extension headers are no longer pushed down the software checksum
path.  It also removes the conditional NETIF_F_HW_CSUM advertisement for
GCS-capable parts and the TSO/GCS mutual exclusion check in
ice_set_features().

A few questions below.
quoted hunk ↗ jump to hunk
diff --git a/drivers/net/ethernet/intel/ice/ice_main.c b/drivers/net/ethernet/intel/ice/ice_main.c
index cc30d8c73a41c..a5e9c3114706f 100644
--- a/drivers/net/ethernet/intel/ice/ice_main.c
+++ b/drivers/net/ethernet/intel/ice/ice_main.c
@@ -3491,9 +3491,8 @@ void ice_set_netdev_features(struct net_device *netdev)
 			NETIF_F_RXHASH;
 
 	csumo_features = NETIF_F_RXCSUM	  |
-			 NETIF_F_IP_CSUM  |
 			 NETIF_F_SCTP_CRC |
-			 NETIF_F_IPV6_CSUM;
+			 NETIF_F_HW_CSUM;
[Severity: Medium]
Should this hunk carry a Fixes: tag and be split out from the GCS/TSO
cleanup?

As far as I can tell this is not just an offload-advertisement cleanup, it
fixes a user-visible drop.  With only NETIF_F_IP_CSUM/NETIF_F_IPV6_CSUM set,
skb_csum_hwoffload_help() in net/core/dev.c forces the software path:

	if (features & (NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM)) {
		if (vlan_get_protocol(skb) == htons(ETH_P_IPV6) &&
		    skb_network_header_len(skb) != sizeof(struct ipv6hdr))
			goto sw_checksum;

and skb_checksum_help() refuses GSO skbs:

	if (unlikely(skb_is_gso(skb))) {
		skb_warn_bad_offload(skb);
		return -EINVAL;
	}

Since ice advertises NETIF_F_TSO6, netif_needs_gso() is false and the skb
arrives still marked GSO, so validate_xmit_skb() takes the
out_kfree_skb path and the frame is dropped rather than de-offloaded.

That means TSO'd IPv6 traffic with an extension header (SRv6, HOPOPTS,
DSTOPTS) is dropped on every ice device on stable kernels containing the
skb_csum_hwoffload_help() change (04c20a9356f2, v6.13+).  The changelog only
hints at this with "To not drop such packets", and there is no Fixes: tag,
so it is hard to identify for backport.  Would it make sense to make this
one-liner a standalone patch with a Fixes: tag, and keep the GCS/TSO
mutual-exclusion removal (which depends on the earlier "ice: always do GCS
if hardware supports it" patch in this series) separate?

[Severity: Medium]
On non-GCS parts, is NETIF_F_HW_CSUM an accurate description of what the
legacy path in ice_tx_csum() (drivers/net/ethernet/intel/ice/ice_txrx.c)
implements?

NETIF_F_HW_CSUM promises the driver honours skb->csum_start /
skb->csum_offset for any protocol, and once it is set
skb_csum_hwoffload_help() returns 0 unconditionally, so the core no longer
checks that skb->csum_offset is offsetof(struct tcphdr, check) or
offsetof(struct udphdr, check).

The legacy branch of ice_tx_csum() derives the insertion point purely from
its own header parse and never looks at skb->csum_offset:

	switch (l4_proto) {
	case IPPROTO_TCP:
		cmd |= ICE_TX_DESC_CMD_L4T_EOFT_TCP;
		l4_len = l4.tcp->doff;
		offset |= l4_len << ICE_TX_DESC_LEN_L4_LEN_S;
		break;
	...
	default:
		goto checksum_sw_fb;
	}

So for a CHECKSUM_PARTIAL frame that parses as IPv4/IPv6 + TCP/UDP but asks
for a different csum_offset (virtio_net_hdr/tap or AF_PACKET with a vnet
header; skb_partial_csum_set() only checks that start/off lie inside the
linear area), hardware writes the checksum at the standard L4 offset while
the field the stack asked to be filled keeps the pseudo-header seed.  The
checksum_sw_fb fallback does not catch this, since it is only reached when
the network header is neither IPv4 nor IPv6 or when l4_proto hits the switch
default.

i40e has the same loose interpretation (it never references
skb->csum_offset either), so this may well be intended precedent - is that
the reasoning here?
quoted hunk ↗ jump to hunk
 
 	vlano_features = NETIF_F_HW_VLAN_CTAG_FILTER |
 			 NETIF_F_HW_VLAN_CTAG_TX     |
@@ -3555,12 +3554,6 @@ void ice_set_netdev_features(struct net_device *netdev)
 	/* Allow core to manage IRQs affinity */
 	netif_set_affinity_auto(netdev);
 
-	/* Mutual exclusivity for TSO and GCS is enforced by the set features
-	 * ndo callback.
-	 */
-	if (ice_is_feature_supported(pf, ICE_F_GCS))
-		netdev->hw_features |= NETIF_F_HW_CSUM;
-
[Severity: High]
With NETIF_F_HW_CSUM now in csumo_features it is enabled by default
(netdev->features |= netdev->hw_features) and also propagated into
hw_enc_features and vlan_features.  On E830 that makes the GCS context
descriptor path in ice_tx_csum() the default Tx checksum path.  Can the
values programmed there be truncated?

	u16 csum_start = (skb->csum_start - skb->mac_header) / 2;
	u16 csum_offset = skb->csum_offset / 2;
	u16 gcs_params;

	gcs_params = FIELD_PREP(ICE_TX_GCS_DESC_START_M, csum_start) |
		     FIELD_PREP(ICE_TX_GCS_DESC_OFFSET_M, csum_offset) |

The descriptor fields in ice_lan_tx_rx.h are narrow:

	#define ICE_TX_GCS_DESC_START_M		GENMASK(7, 0)
	#define ICE_TX_GCS_DESC_OFFSET_M	GENMASK(11, 8)

i.e. at most 510 bytes of L2+L3 header and at most a 30-byte checksum
offset.  Neither value is range-checked, and FIELD_PREP()'s
__BF_FIELD_CHECK only rejects out-of-range constants, so a runtime value is
silently masked.

The driver's ndo_features_check, ice_features_check(), only bounds the two
lengths individually:

	len = skb_network_offset(skb);
	if (len > ICE_TXD_MACLEN_MAX || len & 0x1)
		goto out_rm_features;

	len = skb_network_header_len(skb);
	if (len > ICE_TXD_IPLEN_MAX || len & 0x1)
		goto out_rm_features;

That permits 254 + 508 = 762 combined bytes, well past the 510 bytes the
GCS start field can express.  An IPv6 frame with roughly 498..508 bytes of
header plus extension headers (a long HOPOPTS/DSTOPTS chain, or an SRv6 SRH
with many SIDs over a VLAN) passes both checks, yet
(csum_start - mac_header) / 2 = 259 becomes 3 after masking, so the NIC
sums the wrong byte range and inserts the result inside the MAC/IPv6
header.

The same masking applies to skb->csum_offset > 30, which
virtio_net_hdr_to_skb()/skb_partial_csum_set() allow (they only bound
start/off against skb_headlen()), and which the core no longer filters now
that NETIF_F_HW_CSUM is advertised.  Would a FIELD_MAX() check on both
values before taking the GCS branch, or an added combined-length check in
ice_features_check(), be appropriate here?
quoted hunk ↗ jump to hunk
 	netif_set_tso_max_size(netdev, ICE_MAX_TSO_SIZE);
 }
 
@@ -6519,18 +6512,6 @@ ice_set_features(struct net_device *netdev, netdev_features_t features)
 	if (changed & NETIF_F_LOOPBACK)
 		ret = ice_set_loopback(vsi, !!(features & NETIF_F_LOOPBACK));
 
-	/* Due to E830 hardware limitations, TSO (NETIF_F_ALL_TSO) with GCS
-	 * (NETIF_F_HW_CSUM) is not supported.
-	 */
-	if (ice_is_feature_supported(pf, ICE_F_GCS) &&
-	    ((features & NETIF_F_HW_CSUM) && (features & NETIF_F_ALL_TSO))) {
-		if (netdev->features & NETIF_F_HW_CSUM)
-			dev_err(ice_pf_to_dev(pf), "To enable TSO, you must first disable HW checksum.\n");
-		else
-			dev_err(ice_pf_to_dev(pf), "To enable HW checksum, you must first disable TSO.\n");
-		return -EIO;
-	}
-
 	return ret;
 }
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help