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

From: Justin Lai

Date: Mon Jun 01 2026 - 02:25:07 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.

Signed-off-by: Justin Lai <justinlai0215@xxxxxxxxxxx>
---
drivers/net/ethernet/realtek/rtase/rtase.h | 3 ++
.../net/ethernet/realtek/rtase/rtase_main.c | 50 +++++++++++++++++++
2 files changed, 53 insertions(+)

diff --git a/drivers/net/ethernet/realtek/rtase/rtase.h b/drivers/net/ethernet/realtek/rtase/rtase.h
index 9bd6872474c1..213fe2173b40 100644
--- a/drivers/net/ethernet/realtek/rtase/rtase.h
+++ b/drivers/net/ethernet/realtek/rtase/rtase.h
@@ -363,4 +363,7 @@ struct rtase_private {

#define RTASE_MSS_MASK GENMASK(28, 18)

+#define RTASE_MIN_PAD_LEN 47
+#define RTASE_SHORT_PKT_THRESH 128
+
#endif /* RTASE_H */
diff --git a/drivers/net/ethernet/realtek/rtase/rtase_main.c b/drivers/net/ethernet/realtek/rtase/rtase_main.c
index bde9bccfb5a9..43a4aa275b62 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>
@@ -1250,6 +1251,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)
+{
+ bool has_trans = skb_transport_header_was_set(skb);
+ u32 pkt_len = skb->len;
+ u32 trans_data_len;
+ u16 dest_port;
+ u32 pad_len;
+
+ if (!(has_trans &&
+ pkt_len < RTASE_SHORT_PKT_THRESH + RTASE_MIN_PAD_LEN &&
+ rtase_skb_is_udp(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) {
+ 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)
{
@@ -1363,6 +1410,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