Hi,
Thank you for the detailed findings — the combination of workarounds you identified (1 ms delay, manual DMA flush, and removing dma-coherent) is a textbook fingerprint of a cache alias problem. Here is the root cause explanation and the recommended fix path.
Root cause: cached virtual alias over the TX descriptor region
The LS1021A ENET DMA is a non-cache-coherent bus master — it reads DDR directly, with no visibility into the CPU cache. The correct operating model for gianfar on LS1021A is the non-coherent software-managed model: descriptors allocated in uncached memory, with explicit dma_sync_* calls whenever the CPU updates a descriptor field.
What your LPAE changes most likely introduced is a page table entry for the descriptor physical address range that carries the wrong cache attribute — Normal Writeback instead of Device or Normal Non-cacheable. This creates two virtual aliases to the same physical memory with different cacheability: the allocation path (via dma_alloc_noncoherent) maps it uncached, but the per-packet TX descriptor update path in the send function accesses it through a cached alias. The CPU writes the updated descriptor fields into the cache line, they never reach DDR, and the ENET DMA reads stale data and underruns.
Why your three workarounds all mask the same defect
-
1 ms delay / message insertion: adds enough latency for the CPU writeback buffer to drain naturally under low load — timing-dependent, will fail under traffic or frequency change.
-
Manual dma_flush in the send function: forces a cache clean-to-PoC before the DMA reads the descriptor — correct behaviour, but should not be necessary if the region is genuinely uncached.
-
Removing dma-coherent from the enet device node: causes the kernel to call dma_map_single() / dma_sync_single_for_device() on each TX descriptor before submission, which explicitly cleans the cache — also correct, and explains why the LS1021A-IOT worked reliably without a send-path flush.
The absence of the send-path flush is the missing piece. The LS1021A-IOT worked with dma-coherent removed because the kernel's DMA mapping layer inserted the sync automatically; your prototype does not have that path because the LPAE changes altered the descriptor region's cache attributes without re-introducing the sync.
DDR3L vs DDR4 / frequency difference
This is not the root cause. Different DRAM type and frequency change write latency and buffer drain timing, which is why the failure is more visible on your prototype — but the underlying defect is architectural and will be present on both boards under sufficient load.
Recommended fix
The correct and self-consistent fix is to combine both of the following:
-
Keep dma-coherent removed from the enet device node (non-coherent model). This causes the kernel DMA layer to issue dma_sync_single_for_device() automatically before DMA ownership is transferred for descriptors allocated via dma_alloc_noncoherent.
-
Add explicit dma_sync_single_for_device() calls in the TX send path at the point where TX descriptor fields (status, data_length, data_pointer) are written on each packet. This is the flush you added manually — but it should be formally placed as a dma_sync_single_for_device(dev, desc_dma_addr, sizeof(txbd), DMA_TO_DEVICE) before writing TDAR. This makes the model explicit and correct regardless of how the MMU attributes the descriptor region.
-
Separately, audit the LPAE MMU library change for the AttrIndx / TEX+C+B fields assigned to the physical address range used for ENET descriptors. The descriptor pool should be mapped as Device-nGnRnE (strongly ordered) or Normal Non-cacheable — not Normal Writeback. You can verify the actual attributes in use with the kernel's ptdump debug interface by checking the virtual address of the descriptor pool after allocation.
The DMA flush you added is not a workaround — it is the correct mechanism. The real defect is that it was absent from the send path in the first place, and the LPAE changes exposed this because they altered the effective cacheability of the region.
Regards