From 2ce9f9d369589fcdf790ab869a43713c5b9d7842 Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Thu, 13 Aug 2026 10:45:47 +0200 Subject: [PATCH 1/2] net: macb: exclude software FCS from TX byte statistics Frames going through macb_pad_and_fcs() get padded and four FCS bytes appended, and TX completion then accounts the grown skb->len. tx_bytes is supposed to exclude the FCS, and frames padded by the hardware are counted without the padding anyway, so these frames show up too large in the statistics. Remember the length the stack handed over and use that for the byte counters. BQL stays on the padded skb->len that netdev_tx_sent_queue() saw. Fixes: 653e92a9175e ("net: macb: add support for padding and fcs computation") Signed-off-by: Nicolai Buchwitz --- drivers/net/ethernet/cadence/macb.h | 3 +++ drivers/net/ethernet/cadence/macb_main.c | 17 ++++++++++------- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/drivers/net/ethernet/cadence/macb.h b/drivers/net/ethernet/cadence/macb.h index 8ef7995db60c63..83838c03d97341 100644 --- a/drivers/net/ethernet/cadence/macb.h +++ b/drivers/net/ethernet/cadence/macb.h @@ -996,6 +996,8 @@ struct macb_dma_desc_ptp { * of the frame * @mapping: DMA address of the skb's fragment buffer * @size: size of the DMA mapped buffer + * @skb_len: skb->len as handed over by the stack, before padding and + * software FCS, only set for the last buffer of the frame * @mapped_as_page: true when buffer was mapped with skb_frag_dma_map(), * false when buffer was mapped with dma_map_single() */ @@ -1003,6 +1005,7 @@ struct macb_tx_skb { struct sk_buff *skb; dma_addr_t mapping; size_t size; + unsigned int skb_len; bool mapped_as_page; }; diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c index b6e824531081e8..797440d838032f 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -1355,8 +1355,8 @@ static void macb_tx_error_task(struct work_struct *work) bp->dev->stats.tx_packets++; queue->stats.tx_packets++; packets++; - bp->dev->stats.tx_bytes += skb->len; - queue->stats.tx_bytes += skb->len; + bp->dev->stats.tx_bytes += tx_skb->skb_len; + queue->stats.tx_bytes += tx_skb->skb_len; bytes += skb->len; } } else { @@ -1483,8 +1483,8 @@ static int macb_tx_complete(struct macb_queue *queue, int budget) skb->data); bp->dev->stats.tx_packets++; queue->stats.tx_packets++; - bp->dev->stats.tx_bytes += skb->len; - queue->stats.tx_bytes += skb->len; + bp->dev->stats.tx_bytes += tx_skb->skb_len; + queue->stats.tx_bytes += tx_skb->skb_len; packets++; bytes += skb->len; } @@ -2262,7 +2262,8 @@ static void macb_poll_controller(struct net_device *dev) static unsigned int macb_tx_map(struct macb *bp, struct macb_queue *queue, struct sk_buff *skb, - unsigned int hdrlen) + unsigned int hdrlen, + unsigned int skb_len) { dma_addr_t mapping; unsigned int len, entry, i, tx_head = queue->tx_head; @@ -2351,6 +2352,7 @@ static unsigned int macb_tx_map(struct macb *bp, /* This is the last buffer of the frame: save socket buffer */ tx_skb->skb = skb; + tx_skb->skb_len = skb_len; /* Update TX ring: update buffer descriptors in reverse order * to avoid race condition @@ -2543,7 +2545,7 @@ static netdev_tx_t macb_start_xmit(struct sk_buff *skb, struct net_device *dev) struct macb *bp = netdev_priv(dev); struct macb_queue *queue = &bp->queues[queue_index]; unsigned int desc_cnt, nr_frags, frag_size, f; - unsigned int hdrlen; + unsigned int hdrlen, skb_len; unsigned long flags; bool is_lso; netdev_tx_t ret = NETDEV_TX_OK; @@ -2553,6 +2555,7 @@ static netdev_tx_t macb_start_xmit(struct sk_buff *skb, struct net_device *dev) return ret; } + skb_len = skb->len; if (macb_pad_and_fcs(&skb, dev)) { dev_kfree_skb_any(skb); return ret; @@ -2618,7 +2621,7 @@ static netdev_tx_t macb_start_xmit(struct sk_buff *skb, struct net_device *dev) } /* Map socket buffer for DMA transfer */ - if (!macb_tx_map(bp, queue, skb, hdrlen)) { + if (!macb_tx_map(bp, queue, skb, hdrlen, skb_len)) { dev_kfree_skb_any(skb); goto unlock; } From bbde467d415a31e52a03053199341f51a1bae09a Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Thu, 13 Aug 2026 19:54:23 +0200 Subject: [PATCH 2/2] net: macb: fix zero UDPv4 checksum on transmit The GEM checksum engine writes its raw result into the UDP checksum field, so a UDPv4 checksum that computes to zero goes out as 0x0000. RFC 768 requires: If the computed checksum is zero, it is transmitted as all ones (the equivalent in one's complement arithmetic). An all zero transmitted checksum value means that the transmitter generated no checksum (for debugging or for higher level protocols that don't care). So these packets lose their integrity check and peers treat them as sent without a checksum. Raspberry Pi confirmed in a simulation of the Cadence IP [1] that the engine skips the final substitution exactly for UDP over IPv4. UDPv6 and TCP come out correct. The IP changelog shows no related change, so probably all GEM revisions have the same bug. Clear the checksum offload features for UDPv4 frames in macb_features_check(), the core then completes the checksum in software before handing the frame over. With ip_summed cleared, macb_pad_and_fcs() appends the FCS and the TX_NOCRC descriptor bit keeps the hardware off the frame. TCP and UDPv6 keep the offload. One-step PTP sync packets keep the hardware path because the MAC rewrites their timestamp during transmit, which would invalidate a software checksum. Leave their existing checksum handling unchanged. [1] https://github.com/raspberrypi/linux/issues/7550#issuecomment-5343018065 Fixes: 85ff3d87bf2e ("net/macb: add TX checksum offload feature") Signed-off-by: Nicolai Buchwitz --- drivers/net/ethernet/cadence/macb_main.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c index 797440d838032f..d4a888b7b03ab7 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -2442,6 +2443,17 @@ static netdev_features_t macb_features_check(struct sk_buff *skb, unsigned int nr_frags, f; unsigned int hdrlen; + /* The GEM skips the RFC 768 substitution of 0xffff for a zero + * UDPv4 checksum (UDPv6 is handled), have the core complete + * UDPv4 in software. One-step PTP sync frames keep the hardware + * path, the MAC rewrites their timestamp in flight. + */ + if (skb->ip_summed == CHECKSUM_PARTIAL && + vlan_get_protocol(skb) == htons(ETH_P_IP) && + skb->csum_offset == offsetof(struct udphdr, check) && + !ptp_one_step_sync(skb)) + features &= ~NETIF_F_CSUM_MASK; + /* Validate LSO compatibility */ /* there is only one buffer or protocol is not UDP */