diff options
| author | Serge Semin <fancer.lancer@gmail.com> | 2025-05-31 01:00:00 +0300 |
|---|---|---|
| committer | Serge Semin <fancer.lancer@gmail.com> | 2026-08-13 20:31:12 +0300 |
| commit | e7eb059c05ecb8730e4658d255658894e0fe7901 (patch) | |
| tree | ed44646f410f97c781bc7fd5e51092f8505a0142 | |
| parent | c7d553e7168e40e62796c223ef68dc1a9c09957b (diff) | |
| download | linux-e7eb059c05ecb8730e4658d255658894e0fe7901.tar.gz linux-e7eb059c05ecb8730e4658d255658894e0fe7901.zip | |
net: stmmac: dwmac1000: Fix improper Rx COE type 2 status getting
Rx Checksum Offload Engine has been available in the DW GMAC IP-core since
v3.30a release. Back then the IP-core already provided two types of the
DMA-descriptors to indicate the results of the IP header and payload
checksumming: normal and enhanced. The problem was that both of these
descriptor types didn't have free flags for that. So the engineers
invented the RDES0.{0,5,7} bits permutation (the bits originally
indicating the least important statuses) so if the Rx COE type 2 was
available their semantics was changed to indicate the results of the
engine work. The bits state parsing was developed in the
enh_desc_coe_rdes0() method.
The situation changed in the DW GMAC v3.50a IP-core. Since that release
the controller has supported the _extended_ version of the enhanced
descriptor. It meant that if Rx COE type 2 (so called Full Checksum
Offload Engine) or Advanced Timestamp features were enabled in the
IP-core, the descriptor can have been optionally extended with four more
dwords containing the features status. Due to that the RDES0.{0,5,7} bits
permutation was no required and was dropped in the v3.50a IP-core release.
There wouldn't have been a problem in the situation described above if the
STMMAC driver was left supporting the STM GMAC onlu. But since the commit
84c9f8c41df9 ("net: stmmac: Add ip version to dts bindings") the driver
was declared to support the generic DW GMAC IP-cores which was wrong
without properly implementing the specifics of the newer GMAC revisions.
Let's fix that by parsing the permuted RDES0.{0,5,7} bits on the DW GMACs
earlier than v3.50a. The Rx COE type 2 status parsing of the extended
enhanced descriptor will be fixed later.
The reasonable question is how come there has been no problem officially
spotted so far? Well, most likely that's because the RDES0.{0,5,7} bits
semantics in the extended enhanced descriptor didn't cause any traffic
loses when parsed in the enh_desc_coe_rdes0() method. For instance the
most frequently seen statuses 0x5/0x4 just means Ethernet-II frame
with/without the extended status available in the RDES4-RDES7 dwords.
[1] DesignWare Cores Ethernet MAC Universal Databook, Revision 3.50a,
February 2009, p. 113.
[2] DesignWare Cores Ethernet MAC Universal Databook, Revision 3.73a,
October 2013, p. 188.
[3] Link: https://lore.kernel.org/netdev/20240111-prevent_dsa_tags-v5-1-63e795a4d129@bootlin.com
Fixes: 84c9f8c41df9 ("net: stmmac: Add ip version to dts bindings")
Signed-off-by: Serge Semin <fancer.lancer@gmail.com>
| -rw-r--r-- | drivers/net/ethernet/stmicro/stmmac/descs.h | 1 | ||||
| -rw-r--r-- | drivers/net/ethernet/stmicro/stmmac/descs_com.h | 38 | ||||
| -rw-r--r-- | drivers/net/ethernet/stmicro/stmmac/enh_desc.c | 106 | ||||
| -rw-r--r-- | drivers/net/ethernet/stmicro/stmmac/hwif.c | 6 | ||||
| -rw-r--r-- | drivers/net/ethernet/stmicro/stmmac/hwif.h | 2 | ||||
| -rw-r--r-- | drivers/net/ethernet/stmicro/stmmac/norm_desc.c | 59 | ||||
| -rw-r--r-- | drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 4 |
7 files changed, 156 insertions, 60 deletions
diff --git a/drivers/net/ethernet/stmicro/stmmac/descs.h b/drivers/net/ethernet/stmicro/stmmac/descs.h index e62e2ebcf273..acb765fbaee2 100644 --- a/drivers/net/ethernet/stmicro/stmmac/descs.h +++ b/drivers/net/ethernet/stmicro/stmmac/descs.h @@ -23,6 +23,7 @@ #define RDES0_FRAME_TYPE BIT(5) #define RDES0_COLLISION BIT(6) #define RDES0_IPC_CSUM_ERROR BIT(7) +#define RDES0_GIANT_FRAME_ERROR RDES0_IPC_CSUM_ERROR #define RDES0_LAST_DESCRIPTOR BIT(8) #define RDES0_FIRST_DESCRIPTOR BIT(9) #define RDES0_VLAN_TAG BIT(10) diff --git a/drivers/net/ethernet/stmicro/stmmac/descs_com.h b/drivers/net/ethernet/stmicro/stmmac/descs_com.h index 9d1a94a4fa49..7cb8c20fbe6f 100644 --- a/drivers/net/ethernet/stmicro/stmmac/descs_com.h +++ b/drivers/net/ethernet/stmicro/stmmac/descs_com.h @@ -126,4 +126,42 @@ static inline void norm_set_tx_desc_len_on_chain(struct dma_desc *p, int len) { p->des1 |= cpu_to_le32(len & TDES1_BUFFER1_SIZE_MASK); } + +/* Functions used for all ring/chain modes, enhanced/normal descriptors */ + +static inline int com_desc_rx_coe_rdes0(int ipc_err, int type, int payload_err) +{ + u32 status = (type << 2 | ipc_err << 1 | payload_err) & 0x7; + + /* bits 5 7 0 | Frame status + * ---------------------------------------------------------- + * 0 0 0 | IEEE 802.3 Type frame (length < 1536 octets) + * 1 0 0 | IPv4/6 No CSUM errorS. + * 1 0 1 | IPv4/6 CSUM PAYLOAD error + * 1 1 0 | IPv4/6 CSUM IP HR error + * 1 1 1 | IPv4/6 IP PAYLOAD AND HEADER errorS + * 0 0 1 | IPv4/6 unsupported IP PAYLOAD + * 0 1 1 | COE bypassed.. no IPv4/6 frame + * 0 1 0 | Reserved. + */ + switch (status) { + case 0x0: + return llc_snap; + case 0x1: + return csum_none; + case 0x3: + return csum_none; + case 0x4: + return good_frame; + case 0x5: + return discard_frame; + case 0x6: + return discard_frame; + case 0x7: + return discard_frame; + default: + return csum_none; + } +} + #endif /* __DESC_COM_H__ */ diff --git a/drivers/net/ethernet/stmicro/stmmac/enh_desc.c b/drivers/net/ethernet/stmicro/stmmac/enh_desc.c index 0884fac85f29..56f430f69ffc 100644 --- a/drivers/net/ethernet/stmicro/stmmac/enh_desc.c +++ b/drivers/net/ethernet/stmicro/stmmac/enh_desc.c @@ -75,39 +75,6 @@ static int enh_desc_get_tx_len(struct dma_desc *p) return (le32_to_cpu(p->des1) & ETDES1_BUFFER1_SIZE_MASK); } -static int enh_desc_coe_rdes0(int ipc_err, int type, int payload_err) -{ - int ret = good_frame; - u32 status = (type << 2 | ipc_err << 1 | payload_err) & 0x7; - - /* bits 5 7 0 | Frame status - * ---------------------------------------------------------- - * 0 0 0 | IEEE 802.3 Type frame (length < 1536 octets) - * 1 0 0 | IPv4/6 No CSUM errorS. - * 1 0 1 | IPv4/6 CSUM PAYLOAD error - * 1 1 0 | IPv4/6 CSUM IP HR error - * 1 1 1 | IPv4/6 IP PAYLOAD AND HEADER errorS - * 0 0 1 | IPv4/6 unsupported IP PAYLOAD - * 0 1 1 | COE bypassed.. no IPv4/6 frame - * 0 1 0 | Reserved. - */ - if (status == 0x0) - ret = llc_snap; - else if (status == 0x4) - ret = good_frame; - else if (status == 0x5) - ret = csum_none; - else if (status == 0x6) - ret = csum_none; - else if (status == 0x7) - ret = csum_none; - else if (status == 0x1) - ret = discard_frame; - else if (status == 0x3) - ret = discard_frame; - return ret; -} - static void enh_desc_get_ext_status(struct stmmac_extra_stats *x, struct dma_extended_desc *p) { @@ -172,10 +139,9 @@ static void enh_desc_get_ext_status(struct stmmac_extra_stats *x, } } -static int enh_desc_get_rx_status(struct stmmac_extra_stats *x, - struct dma_desc *p) +static int enh_desc_get_rx_basic_status(unsigned int rdes0, + struct stmmac_extra_stats *x) { - u32 rdes0 = le32_to_cpu(p->des0); int ret = good_frame; if (unlikely(rdes0 & RDES0_OWN)) @@ -194,11 +160,12 @@ static int enh_desc_get_rx_status(struct stmmac_extra_stats *x, if (unlikely(rdes0 & RDES0_OVERFLOW_ERROR)) x->rx_gmac_overflow++; - if (unlikely(rdes0 & RDES0_IPC_CSUM_ERROR)) - pr_err("\tIPC Csum Error/Giant frame\n"); - if (unlikely(rdes0 & RDES0_COLLISION)) x->rx_collision++; + + if (unlikely(rdes0 & RDES0_GIANT_FRAME_ERROR)) + x->rx_length++; + if (unlikely(rdes0 & RDES0_RECEIVE_WATCHDOG)) x->rx_watchdog++; @@ -211,15 +178,6 @@ static int enh_desc_get_rx_status(struct stmmac_extra_stats *x, ret = discard_frame; } - /* After a payload csum error, the ES bit is set. - * It doesn't match with the information reported into the databook. - * At any rate, we need to understand if the CSUM hw computation is ok - * and report this info to the upper layers. */ - if (likely(ret == good_frame)) - ret = enh_desc_coe_rdes0(!!(rdes0 & RDES0_IPC_CSUM_ERROR), - !!(rdes0 & RDES0_FRAME_TYPE), - !!(rdes0 & ERDES0_RX_MAC_ADDR)); - if (unlikely(rdes0 & RDES0_DRIBBLING)) x->dribbling_bit++; @@ -243,6 +201,33 @@ static int enh_desc_get_rx_status(struct stmmac_extra_stats *x, return ret; } +static int enh_desc_get_rx_status_nocoe(struct stmmac_extra_stats *x, + struct dma_desc *p) +{ + return enh_desc_get_rx_basic_status(le32_to_cpu(p->des0), x); +} + +static int enh_desc_get_rx_status_noext(struct stmmac_extra_stats *x, + struct dma_desc *p) +{ + unsigned int rdes0 = le32_to_cpu(p->des0); + int ret; + + ret = enh_desc_get_rx_basic_status(rdes0, x); + if (ret & (dma_own | rx_not_ls)) + return ret; + + /* Rx COE type 2 has been available since v3.30a. If it's synthesized + * into the GMAC the Bits 5, 7, and 0 state reflects the Rx COE + * outcome. The bits permutation had been available up to the DW GMAC + * v3.50a IP-core release which initially introduced the extended + * enhanced descriptors. + */ + return ret | com_desc_rx_coe_rdes0(!!(rdes0 & RDES0_IPC_CSUM_ERROR), + !!(rdes0 & RDES0_FRAME_TYPE), + !!(rdes0 & RDES0_PAYLOAD_CSUM_ERR)); +} + static void enh_desc_init_rx_desc(struct dma_desc *p, int disable_rx_ic, int mode, int end, int bfsize) { @@ -428,7 +413,7 @@ static void enh_desc_clear(struct dma_desc *p) const struct stmmac_desc_ops enh_desc_ops = { .tx_status = enh_desc_get_tx_status, - .rx_status = enh_desc_get_rx_status, + .rx_status = enh_desc_get_rx_status_nocoe, .get_tx_len = enh_desc_get_tx_len, .init_rx_desc = enh_desc_init_rx_desc, .init_tx_desc = enh_desc_init_tx_desc, @@ -449,3 +434,26 @@ const struct stmmac_desc_ops enh_desc_ops = { .set_addr = enh_desc_set_addr, .clear = enh_desc_clear, }; + +const struct stmmac_desc_ops enh_desc_noext_ops = { + .tx_status = enh_desc_get_tx_status, + .rx_status = enh_desc_get_rx_status_noext, + .get_tx_len = enh_desc_get_tx_len, + .init_rx_desc = enh_desc_init_rx_desc, + .init_tx_desc = enh_desc_init_tx_desc, + .get_tx_owner = enh_desc_get_tx_owner, + .release_tx_desc = enh_desc_release_tx_desc, + .prepare_tx_desc = enh_desc_prepare_tx_desc, + .set_tx_ic = enh_desc_set_tx_ic, + .get_tx_ls = enh_desc_get_tx_ls, + .set_tx_owner = enh_desc_set_tx_owner, + .set_rx_owner = enh_desc_set_rx_owner, + .get_rx_frame_len = enh_desc_get_rx_frame_len, + .enable_tx_timestamp = enh_desc_enable_tx_timestamp, + .get_tx_timestamp_status = enh_desc_get_tx_timestamp_status, + .get_timestamp = enh_desc_get_timestamp, + .get_rx_timestamp_status = enh_desc_get_rx_timestamp_status, + .display_ring = enh_desc_display_ring, + .set_addr = enh_desc_set_addr, + .clear = enh_desc_clear, +}; diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.c b/drivers/net/ethernet/stmicro/stmmac/hwif.c index 7e69ff4b9a98..38cebde51930 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.c +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.c @@ -77,14 +77,14 @@ static int stmmac_dwmac1_quirks(struct stmmac_priv *priv) if (priv->synopsys_id >= DWMAC_CORE_3_50) { dev_info(priv->device, "Enabled extended descriptors\n"); priv->extend_desc = 1; + mac->desc = &enh_desc_ops; } else { dev_warn(priv->device, "Extended descriptors not supported\n"); + mac->desc = &enh_desc_noext_ops; } - - mac->desc = &enh_desc_ops; } else { dev_info(priv->device, "Normal descriptors\n"); - mac->desc = &ndesc_ops; + mac->desc = priv->plat->rx_coe ? &ndesc_rxcoe2_ops : &ndesc_ops; } stmmac_dwmac_mode_quirk(priv); diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.h b/drivers/net/ethernet/stmicro/stmmac/hwif.h index c15fa1cafdea..ca362ab0f61d 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.h +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.h @@ -677,7 +677,9 @@ struct stmmac_regs_off { }; extern const struct stmmac_desc_ops enh_desc_ops; +extern const struct stmmac_desc_ops enh_desc_noext_ops; extern const struct stmmac_desc_ops ndesc_ops; +extern const struct stmmac_desc_ops ndesc_rxcoe2_ops; extern const struct stmmac_hwtimestamp stmmac_ptp; extern const struct stmmac_hwtimestamp dwmac1000_ptp; diff --git a/drivers/net/ethernet/stmicro/stmmac/norm_desc.c b/drivers/net/ethernet/stmicro/stmmac/norm_desc.c index 4fdf75c6bab0..fa95a1eeff5e 100644 --- a/drivers/net/ethernet/stmicro/stmmac/norm_desc.c +++ b/drivers/net/ethernet/stmicro/stmmac/norm_desc.c @@ -64,10 +64,9 @@ static int ndesc_get_tx_len(struct dma_desc *p) * and, if required, updates the multicast statistics. * In case of success, it returns good_frame because the GMAC device * is supposed to be able to compute the csum in HW. */ -static int ndesc_get_rx_status(struct stmmac_extra_stats *x, - struct dma_desc *p) +static int ndesc_get_rx_basic_status(unsigned int rdes0, + struct stmmac_extra_stats *x) { - u32 rdes0 = le32_to_cpu(p->des0); int ret = good_frame; if (unlikely(rdes0 & RDES0_OWN)) @@ -85,8 +84,8 @@ static int ndesc_get_rx_status(struct stmmac_extra_stats *x, x->sa_filter_fail++; if (unlikely(rdes0 & RDES0_OVERFLOW_ERROR)) x->overflow_error++; - if (unlikely(rdes0 & RDES0_IPC_CSUM_ERROR)) - x->ipc_csum_error++; + if (unlikely(rdes0 & RDES0_GIANT_FRAME_ERROR)) + x->rx_length++; if (unlikely(rdes0 & RDES0_COLLISION)) { x->rx_collision++; } @@ -113,6 +112,31 @@ static int ndesc_get_rx_status(struct stmmac_extra_stats *x, return ret; } +static int ndesc_get_rx_status_nocoe(struct stmmac_extra_stats *x, + struct dma_desc *p) +{ + return ndesc_get_rx_basic_status(le32_to_cpu(p->des0), x); +} + +static int ndesc_get_rx_status_coe2(struct stmmac_extra_stats *x, + struct dma_desc *p) +{ + unsigned int rdes0 = le32_to_cpu(p->des0); + int ret; + + ret = ndesc_get_rx_basic_status(rdes0, x); + if (ret & (dma_own | rx_not_ls)) + return ret; + + /* Rx COE type 2 has been available since v3.30a. If it's synthesized + * into the GMAC the Bits 5, 7, and 0 state reflects the Rx COE + * outcome. + */ + return ret | com_desc_rx_coe_rdes0(!!(rdes0 & RDES0_IPC_CSUM_ERROR), + !!(rdes0 & RDES0_FRAME_TYPE), + !!(rdes0 & RDES0_PAYLOAD_CSUM_ERR)); +} + static void ndesc_init_rx_desc(struct dma_desc *p, int disable_rx_ic, int mode, int end, int bfsize) { @@ -279,7 +303,30 @@ static void ndesc_clear(struct dma_desc *p) const struct stmmac_desc_ops ndesc_ops = { .tx_status = ndesc_get_tx_status, - .rx_status = ndesc_get_rx_status, + .rx_status = ndesc_get_rx_status_nocoe, + .get_tx_len = ndesc_get_tx_len, + .init_rx_desc = ndesc_init_rx_desc, + .init_tx_desc = ndesc_init_tx_desc, + .get_tx_owner = ndesc_get_tx_owner, + .release_tx_desc = ndesc_release_tx_desc, + .prepare_tx_desc = ndesc_prepare_tx_desc, + .set_tx_ic = ndesc_set_tx_ic, + .get_tx_ls = ndesc_get_tx_ls, + .set_tx_owner = ndesc_set_tx_owner, + .set_rx_owner = ndesc_set_rx_owner, + .get_rx_frame_len = ndesc_get_rx_frame_len, + .enable_tx_timestamp = ndesc_enable_tx_timestamp, + .get_tx_timestamp_status = ndesc_get_tx_timestamp_status, + .get_timestamp = ndesc_get_timestamp, + .get_rx_timestamp_status = ndesc_get_rx_timestamp_status, + .display_ring = ndesc_display_ring, + .set_addr = ndesc_set_addr, + .clear = ndesc_clear, +}; + +const struct stmmac_desc_ops ndesc_rxcoe2_ops = { + .tx_status = ndesc_get_tx_status, + .rx_status = ndesc_get_rx_status_coe2, .get_tx_len = ndesc_get_tx_len, .init_rx_desc = ndesc_init_rx_desc, .init_tx_desc = ndesc_init_tx_desc, diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 8235401a8e3b..1042a3657b2a 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -5556,7 +5556,7 @@ read_again: if (priv->extend_desc) stmmac_rx_extended_status(priv, &priv->xstats, rx_q->dma_erx + entry); - if (unlikely(status == discard_frame)) { + if (unlikely(status & discard_frame)) { dirty++; error = 1; if (!priv->hwts_rx_en) @@ -5741,7 +5741,7 @@ read_again: if (priv->extend_desc) stmmac_rx_extended_status(priv, &priv->xstats, rx_q->dma_erx + entry); - if (unlikely(status == discard_frame)) { + if (unlikely(status & discard_frame)) { error = 1; if (!priv->hwts_rx_en) rx_errors++; |
