[PATCH net v2] rtase: Workaround for IP fragmented UDP packet hardware bug

From: Justin Lai

Date: Thu Jun 04 2026 - 06:20:01 EST


The hardware parser incorrectly interprets 319/320 in a short
IP fragmented UDP packet payload as standard PTP destination
ports and treats the fragment as a PTP packet for further
parsing.

If the transport data is smaller than RTASE_MIN_PAD_LEN, the
remaining data is insufficient for further parsing and causes
hardware TX hang.

Pad these packets so the transport data reaches
RTASE_MIN_PAD_LEN before transmitting to avoid triggering the
hardware issue.

Fixes: d6e882b89fdf ("rtase: Implement .ndo_start_xmit function")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Justin Lai <justinlai0215@xxxxxxxxxxx>
---
v1 -> v2:
- Remove RTASE_SHORT_PKT_THRESH and the packet length check.
- Check transport data length before parsing the UDP header.
- Add Fixes tag.
- Add Cc: stable@xxxxxxxxxxxxxxx.
- Target net tree.
---
drivers/net/ethernet/realtek/rtase/rtase.h | 2 +
.../net/ethernet/realtek/rtase/rtase_main.c | 50 +++++++++++++++++++
2 files changed, 52 insertions(+)

diff --git a/drivers/net/ethernet/realtek/rtase/rtase.h b/drivers/net/ethernet/realtek/rtase/rtase.h
index b9209eb6ea73..d489d20177ac 100644
--- a/drivers/net/ethernet/realtek/rtase/rtase.h
+++ b/drivers/net/ethernet/realtek/rtase/rtase.h
@@ -359,4 +359,6 @@ struct rtase_private {

#define RTASE_MSS_MASK GENMASK(28, 18)

+#define RTASE_MIN_PAD_LEN 47
+
#endif /* RTASE_H */
diff --git a/drivers/net/ethernet/realtek/rtase/rtase_main.c b/drivers/net/ethernet/realtek/rtase/rtase_main.c
index 55105d34bc79..6c189b3efab0 100644
--- a/drivers/net/ethernet/realtek/rtase/rtase_main.c
+++ b/drivers/net/ethernet/realtek/rtase/rtase_main.c
@@ -61,6 +61,7 @@
#include <linux/pci.h>
#include <linux/pm_runtime.h>
#include <linux/prefetch.h>
+#include <linux/ptp_classify.h>
#include <linux/rtnetlink.h>
#include <linux/tcp.h>
#include <asm/irq.h>
@@ -1249,6 +1250,52 @@ static u32 rtase_tx_csum(struct sk_buff *skb, const struct net_device *dev)
return csum_cmd;
}

+static bool rtase_skb_is_udp(struct sk_buff *skb)
+{
+ int no = skb_network_offset(skb);
+ struct ipv6hdr *i6h, _i6h;
+ struct iphdr *ih, _ih;
+
+ switch (vlan_get_protocol(skb)) {
+ case htons(ETH_P_IP):
+ ih = skb_header_pointer(skb, no, sizeof(_ih), &_ih);
+ return ih && ih->protocol == IPPROTO_UDP;
+ case htons(ETH_P_IPV6):
+ i6h = skb_header_pointer(skb, no, sizeof(_i6h), &_i6h);
+ return i6h && i6h->nexthdr == IPPROTO_UDP;
+ default:
+ return false;
+ }
+}
+
+static bool rtase_skb_pad(struct sk_buff *skb)
+{
+ u32 trans_data_len;
+ u16 dest_port;
+ u32 pad_len;
+
+ if (!skb_transport_header_was_set(skb))
+ return true;
+
+ trans_data_len = skb_tail_pointer(skb) - skb_transport_header(skb);
+ if (trans_data_len < offsetof(struct udphdr, len) ||
+ trans_data_len >= RTASE_MIN_PAD_LEN)
+ return true;
+
+ if (!rtase_skb_is_udp(skb))
+ return true;
+
+ dest_port = ntohs(udp_hdr(skb)->dest);
+
+ if (dest_port == PTP_EV_PORT || dest_port == PTP_GEN_PORT) {
+ pad_len = RTASE_MIN_PAD_LEN - trans_data_len;
+ if (__skb_put_padto(skb, skb->len + pad_len, false))
+ return false;
+ }
+
+ return true;
+}
+
static int rtase_xmit_frags(struct rtase_ring *ring, struct sk_buff *skb,
u32 opts1, u32 opts2)
{
@@ -1362,6 +1409,9 @@ static netdev_tx_t rtase_start_xmit(struct sk_buff *skb,
opts2 |= rtase_tx_csum(skb, dev);
}

+ if (!rtase_skb_pad(skb))
+ goto err_dma_0;
+
frags = rtase_xmit_frags(ring, skb, opts1, opts2);
if (unlikely(frags < 0))
goto err_dma_0;
--
2.40.1