[PATCH v1] net: xilinx: axi_emac: DMA transfer termination fix

bigunclemax at gmail.com bigunclemax at gmail.com
Thu Sep 19 19:00:12 CEST 2024


From: Maksim Kiselev <bigunclemax at gmail.com>

According to Xilinx AXI DMA Spec:
"There can be a lag of time between when DMACR.RS = 0 and when
DMASR.Halted = 1"

So to ensure that DMA transfer is really terminated we need to wait
for DMASR.Halted status, which was missed in the Xilinx AXI EMAC driver.

This issue resulted in Linux kernel vmalloc area corruption by AXI Rx DMA
Buffer Descriptor caused by the occasional Ethernet packet arrival in case
the Linux image was loaded via tftp and then immediately launched.

Signed-off-by: Alexander Razinkov <amrazinkov at ya.ru>
Signed-off-by: Maksim Kiselev <bigunclemax at gmail.com>
---
 drivers/net/xilinx_axi_emac.c | 62 +++++++++++++++++++++--------------
 1 file changed, 38 insertions(+), 24 deletions(-)

diff --git a/drivers/net/xilinx_axi_emac.c b/drivers/net/xilinx_axi_emac.c
index 4d87e2d1f3..62af64b1eb 100644
--- a/drivers/net/xilinx_axi_emac.c
+++ b/drivers/net/xilinx_axi_emac.c
@@ -453,11 +453,33 @@ static int setup_phy(struct udevice *dev)
 	return 1;
 }
 
+/* Reset DMA engine */
+static void axi_dma_reset(struct axidma_priv *priv)
+{
+	u32 timeout = 500;
+
+	/* Reset the engine so the hardware starts from a known state */
+	writel(XAXIDMA_CR_RESET_MASK, &priv->dmatx->control);
+	writel(XAXIDMA_CR_RESET_MASK, &priv->dmarx->control);
+
+	/* At the initialization time, hardware should finish reset quickly */
+	while (timeout--) {
+		/* Check transmit/receive channel */
+		/* Reset is done when the reset bit is low */
+		if (!((readl(&priv->dmatx->control) |
+		    readl(&priv->dmarx->control)) & XAXIDMA_CR_RESET_MASK))
+			break;
+	}
+	if (!timeout)
+		printf("%s: DMA reset timeout!\n", __func__);
+}
+
 /* STOP DMA transfers */
 static void axiemac_stop(struct udevice *dev)
 {
 	struct axidma_priv *priv = dev_get_priv(dev);
 	u32 temp;
+	int count;
 
 	/* Stop the hardware */
 	temp = readl(&priv->dmatx->control);
@@ -468,6 +490,21 @@ static void axiemac_stop(struct udevice *dev)
 	temp &= ~XAXIDMA_CR_RUNSTOP_MASK;
 	writel(temp, &priv->dmarx->control);
 
+	/* Give DMAs a chance to halt gracefully */
+	temp = readl(&priv->dmarx->status);
+	for (count = 0; !(temp & XAXIDMA_HALTED_MASK) && count < 5; ++count) {
+		mdelay(20);
+		temp = readl(&priv->dmarx->status);
+	}
+
+	temp = readl(&priv->dmatx->status);
+	for (count = 0; !(temp & XAXIDMA_HALTED_MASK) && count < 5; ++count) {
+		mdelay(20);
+		temp = readl(&priv->dmatx->status);
+	}
+
+	axi_dma_reset(priv);
+
 	debug("axiemac: Halted\n");
 }
 
@@ -552,29 +589,6 @@ static int axiemac_write_hwaddr(struct udevice *dev)
 	return 0;
 }
 
-/* Reset DMA engine */
-static void axi_dma_init(struct axidma_priv *priv)
-{
-	u32 timeout = 500;
-
-	/* Reset the engine so the hardware starts from a known state */
-	writel(XAXIDMA_CR_RESET_MASK, &priv->dmatx->control);
-	writel(XAXIDMA_CR_RESET_MASK, &priv->dmarx->control);
-
-	/* At the initialization time, hardware should finish reset quickly */
-	while (timeout--) {
-		/* Check transmit/receive channel */
-		/* Reset is done when the reset bit is low */
-		if (!((readl(&priv->dmatx->control) |
-				readl(&priv->dmarx->control))
-						& XAXIDMA_CR_RESET_MASK)) {
-			break;
-		}
-	}
-	if (!timeout)
-		printf("%s: Timeout\n", __func__);
-}
-
 static int axiemac_start(struct udevice *dev)
 {
 	struct axidma_priv *priv = dev_get_priv(dev);
@@ -587,7 +601,7 @@ static int axiemac_start(struct udevice *dev)
 	 * reset, and since AXIDMA reset line is connected to AxiEthernet, this
 	 * would ensure a reset of AxiEthernet.
 	 */
-	axi_dma_init(priv);
+	axi_dma_reset(priv);
 
 	/* Initialize AxiEthernet hardware. */
 	if (priv->mactype == EMAC_1G) {
-- 
2.45.2



More information about the U-Boot mailing list