diff mbox series

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

Message ID 20240919170022.203450-1-bigunclemax@gmail.com
State New
Headers show
Series [v1] net: xilinx: axi_emac: DMA transfer termination fix | expand

Commit Message

Maxim Kiselev Sept. 19, 2024, 5 p.m. UTC
From: Maksim Kiselev <bigunclemax@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@ya.ru>
Signed-off-by: Maksim Kiselev <bigunclemax@gmail.com>
---
 drivers/net/xilinx_axi_emac.c | 62 +++++++++++++++++++++--------------
 1 file changed, 38 insertions(+), 24 deletions(-)
diff mbox series

Patch

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) {