Re: [PATCH net-next 03/15] ice: use NETIF_F_HW_CSUM instead of IP/IPV6
Jakub Kicinski <[email protected]>
| Newsgroups | org.kernel.vger.netdev |
|---|---|
| Message-ID | <[email protected]> |
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.
> 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?
>
> 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?
> 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;
> }