summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSerge Semin <fancer.lancer@gmail.com>2025-05-31 01:00:00 +0300
committerSerge Semin <fancer.lancer@gmail.com>2026-08-13 20:31:12 +0300
commite7eb059c05ecb8730e4658d255658894e0fe7901 (patch)
treeed44646f410f97c781bc7fd5e51092f8505a0142
parentc7d553e7168e40e62796c223ef68dc1a9c09957b (diff)
downloadlinux-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.h1
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/descs_com.h38
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/enh_desc.c106
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/hwif.c6
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/hwif.h2
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/norm_desc.c59
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/stmmac_main.c4
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++;