From bcda4cbf1dfb5237946dc7f228fd5dd4aff383a0 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 that take the macb_pad_and_fcs() path are padded and get four FCS bytes appended, and the TX completion paths account the resulting skb->len. tx_bytes must exclude the FCS, and frames padded by the hardware are counted without the padding, so these frames are over-counted by the FCS and any padding added. Save the length as handed over by the stack and use it for the byte statistics. BQL keeps working on the padded skb->len, which netdev_tx_sent_queue() saw as well. 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 ecebe6cee31e007b488b57f5326d7f81215c8168 Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Thu, 13 Aug 2026 10:11:46 +0200 Subject: [PATCH 2/2] net: macb: fix zero UDP checksum on transmit The GEM transmit checksum engine inserts the raw one's complement result into the checksum field. When a UDP checksum computes to zero, RFC 768 requires 0xffff on the wire, zero means the sender generated no checksum. The engine sends 0x0000 instead, which turns off the integrity check for IPv4 and makes receivers drop the datagram for IPv6. This hits roughly one in 65536 UDP packets. Complete UDP checksums in software. The skb then continues with ip_summed cleared, so macb_pad_and_fcs() appends the FCS and the TX_NOCRC descriptor bit keeps the hardware from modifying the frame. TCP keeps the offload, a zero TCP checksum is valid there. One-step PTP sync packets stay on the hardware path because the MAC rewrites their timestamp during transmit and would invalidate a software checksum. The zero checksum case remains for those packets. Fixes: 85ff3d87bf2e ("net/macb: add TX checksum offload feature") Link: https://github.com/raspberrypi/linux/issues/7550 Signed-off-by: Nicolai Buchwitz --- drivers/net/ethernet/cadence/macb_main.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c index 797440d838032f..31e003a9372115 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -2476,6 +2476,13 @@ static inline int macb_clear_csum(struct sk_buff *skb) if (skb->ip_summed != CHECKSUM_PARTIAL) return 0; + /* The hardware lacks the RFC 768 substitution of 0xffff for a + * UDP checksum that computes to zero, complete UDP in software. + */ + if (skb->csum_offset == offsetof(struct udphdr, check) && + !ptp_one_step_sync(skb)) + return skb_checksum_help(skb); + /* make sure we can modify the header */ if (unlikely(skb_cow_head(skb, 0))) return -1;