From 76c4b6e96c5b5dfb2c5584dcea8c149534e1df47 Mon Sep 17 00:00:00 2001 From: Samuel Morris Date: Wed, 3 Jun 2026 16:19:37 -0700 Subject: [PATCH] net: fec: add imx8mm devinfo with UDP checksum quirk The imx8mm FEC hardware RX checksum offload engine incorrectly marks certain UDP packets as checksum-valid, causing them to be silently dropped by the kernel UDP stack. Add a dedicated fsl,imx8mm-fec compatible string, which previously fell through to fsl,imx8mq-fec, and a new FEC_QUIRK_ERR_UDP_CSUM quirk. When the quirk is set, downgrade CHECKSUM_UNNECESSARY to CHECKSUM_NONE for UDP packets only after the HW reports no error, forcing the kernel UDP stack to re-validate the checksum in software. TCP checksum offload is unaffected. --- drivers/net/ethernet/freescale/fec.h | 6 +++++ drivers/net/ethernet/freescale/fec_main.c | 27 ++++++++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/freescale/fec.h b/drivers/net/ethernet/freescale/fec.h index 423838c8a27ae..df33dc711ebd0 100644 --- a/drivers/net/ethernet/freescale/fec.h +++ b/drivers/net/ethernet/freescale/fec.h @@ -514,6 +514,12 @@ struct bufdesc_ex { */ #define FEC_QUIRK_HAS_MDIO_C45 BIT(24) +/* i.MX8MM FEC hardware RX checksum offload incorrectly validates certain UDP + * packets, causing them to be silently dropped by the kernel UDP stack. + * Force software re-validation for UDP by downgrading CHECKSUM_UNNECESSARY. + */ +#define FEC_QUIRK_ERR_UDP_CSUM BIT(25) + struct bufdesc_prop { int qid; /* Address of Rx and Tx buffers */ diff --git a/drivers/net/ethernet/freescale/fec_main.c b/drivers/net/ethernet/freescale/fec_main.c index b445ae835849d..2bee5ccc0cb75 100644 --- a/drivers/net/ethernet/freescale/fec_main.c +++ b/drivers/net/ethernet/freescale/fec_main.c @@ -154,6 +154,17 @@ static const struct fec_devinfo fec_imx6ul_info = { FEC_QUIRK_HAS_MDIO_C45, }; +static const struct fec_devinfo fec_imx8mm_info = { + .quirks = FEC_QUIRK_ENET_MAC | FEC_QUIRK_HAS_GBIT | + FEC_QUIRK_HAS_BUFDESC_EX | FEC_QUIRK_HAS_CSUM | + FEC_QUIRK_HAS_VLAN | FEC_QUIRK_HAS_AVB | + FEC_QUIRK_ERR007885 | FEC_QUIRK_BUG_CAPTURE | + FEC_QUIRK_HAS_RACC | FEC_QUIRK_HAS_COALESCE | + FEC_QUIRK_CLEAR_SETUP_MII | FEC_QUIRK_HAS_MULTI_QUEUES | + FEC_QUIRK_HAS_EEE | FEC_QUIRK_WAKEUP_FROM_INT2 | + FEC_QUIRK_HAS_MDIO_C45 | FEC_QUIRK_ERR_UDP_CSUM, +}; + static const struct fec_devinfo fec_imx8mq_info = { .quirks = FEC_QUIRK_ENET_MAC | FEC_QUIRK_HAS_GBIT | FEC_QUIRK_HAS_BUFDESC_EX | FEC_QUIRK_HAS_CSUM | @@ -209,6 +220,9 @@ static struct platform_device_id fec_devtype[] = { }, { .name = "imx6ul-fec", .driver_data = (kernel_ulong_t)&fec_imx6ul_info, + }, { + .name = "imx8mm-fec", + .driver_data = (kernel_ulong_t)&fec_imx8mm_info, }, { .name = "imx8mq-fec", .driver_data = (kernel_ulong_t)&fec_imx8mq_info, @@ -232,6 +246,7 @@ enum imx_fec_type { MVF600_FEC, IMX6SX_FEC, IMX6UL_FEC, + IMX8MM_FEC, IMX8MQ_FEC, IMX8QM_FEC, S32V234_FEC, @@ -245,6 +260,7 @@ static const struct of_device_id fec_dt_ids[] = { { .compatible = "fsl,mvf600-fec", .data = &fec_devtype[MVF600_FEC], }, { .compatible = "fsl,imx6sx-fec", .data = &fec_devtype[IMX6SX_FEC], }, { .compatible = "fsl,imx6ul-fec", .data = &fec_devtype[IMX6UL_FEC], }, + { .compatible = "fsl,imx8mm-fec", .data = &fec_devtype[IMX8MM_FEC], }, { .compatible = "fsl,imx8mq-fec", .data = &fec_devtype[IMX8MQ_FEC], }, { .compatible = "fsl,imx8qm-fec", .data = &fec_devtype[IMX8QM_FEC], }, { .compatible = "fsl,s32v234-fec", .data = &fec_devtype[S32V234_FEC], }, @@ -1786,8 +1802,17 @@ fec_enet_rx_queue(struct net_device *ndev, int budget, u16 queue_id) if (fep->bufdesc_ex && (fep->csum_flags & FLAG_RX_CSUM_ENABLED)) { if (!(ebdp->cbd_esc & cpu_to_fec32(FLAG_RX_CSUM_ERROR))) { - /* don't check it */ skb->ip_summed = CHECKSUM_UNNECESSARY; + /* i.MX8MM FEC HW checksum offload incorrectly + * validates certain UDP packets (FEC_QUIRK_ERR_UDP_CSUM). + * Force software re-validation for UDP only. + */ + if (fep->quirks & FEC_QUIRK_ERR_UDP_CSUM) { + struct iphdr *iph = (struct iphdr *)skb_network_header(skb); + + if (iph && iph->protocol == IPPROTO_UDP) + skb->ip_summed = CHECKSUM_NONE; + } } else { skb_checksum_none_assert(skb); }