The dma_map_single() function calls one of the functions
  * invalidate_dcache_range(),
  * flush_dcache_range().
Both of them expect that 'vaddr' is aligned to the ARCH_DMA_MINALIGN
boundary. Unfortunately, RX/TX descriptors are 32-byte long. Thus they
might not be aligned to the ARCH_DMA_MINALIGN boundary. Data flushing
(or invalidating) might do nothing in this case.

The same applies to dma_unmap_single() function.

In the TX path case the issue might prevent package transmission (filled
TX descriptor was not flushed).

To fix an issue a special wrappers for
  * dma_map_single(),
  * dma_unmap_single()
functions were created. The patch fix flushing/invalidatiog for the
RX path as well.

The bug appears on 32-bit airoha platform, but should be present on
64-bit as well.

The code was tested both on 32-bit and 64-bit airoha boards.

Signed-off-by: Mikhail Kshevetskiy <mikhail.kshevets...@iopsys.eu>
---
 drivers/net/airoha_eth.c | 33 +++++++++++++++++++++++++++------
 1 file changed, 27 insertions(+), 6 deletions(-)

diff --git a/drivers/net/airoha_eth.c b/drivers/net/airoha_eth.c
index 2aa42b3d727..e18816b2943 100644
--- a/drivers/net/airoha_eth.c
+++ b/drivers/net/airoha_eth.c
@@ -354,6 +354,27 @@ static u32 airoha_rmw(void __iomem *base, u32 offset, u32 
mask, u32 val)
 #define airoha_switch_wr(eth, offset, val)                     \
        airoha_wr((eth)->switch_regs, (offset), (val))
 
+static inline dma_addr_t dma_map_unaligned(void *vaddr, size_t len,
+                                          enum dma_data_direction dir)
+{
+       uintptr_t start, end;
+
+       start = ALIGN_DOWN((uintptr_t)vaddr, ARCH_DMA_MINALIGN);
+       end = ALIGN((uintptr_t)(vaddr + len), ARCH_DMA_MINALIGN);
+
+       return dma_map_single((void *)start, end - start, dir);
+}
+
+static inline void dma_unmap_unaligned(dma_addr_t addr, size_t len,
+                                      enum dma_data_direction dir)
+{
+       uintptr_t start, end;
+
+       start = ALIGN_DOWN((uintptr_t)addr, ARCH_DMA_MINALIGN);
+       end = ALIGN((uintptr_t)(addr + len), ARCH_DMA_MINALIGN);
+       dma_unmap_single(start, end - start, dir);
+}
+
 static void airoha_fe_maccr_init(struct airoha_eth *eth)
 {
        int p;
@@ -391,7 +412,7 @@ static void airoha_qdma_reset_rx_desc(struct airoha_queue 
*q, int index,
        val = FIELD_PREP(QDMA_DESC_LEN_MASK, PKTSIZE_ALIGN);
        WRITE_ONCE(desc->ctrl, cpu_to_le32(val));
 
-       dma_map_single(desc, sizeof(*desc), DMA_TO_DEVICE);
+       dma_map_unaligned(desc, sizeof(*desc), DMA_TO_DEVICE);
 }
 
 static void airoha_qdma_init_rx_desc(struct airoha_queue *q)
@@ -826,14 +847,14 @@ static int airoha_eth_send(struct udevice *dev, void 
*packet, int length)
        WRITE_ONCE(desc->msg1, cpu_to_le32(msg1));
        WRITE_ONCE(desc->msg2, cpu_to_le32(0xffff));
 
-       dma_map_single(desc, sizeof(*desc), DMA_TO_DEVICE);
+       dma_map_unaligned(desc, sizeof(*desc), DMA_TO_DEVICE);
 
        airoha_qdma_rmw(qdma, REG_TX_CPU_IDX(qid), TX_RING_CPU_IDX_MASK,
                        FIELD_PREP(TX_RING_CPU_IDX_MASK, index));
 
        for (i = 0; i < 100; i++) {
-               dma_unmap_single(virt_to_phys(desc), sizeof(*desc),
-                                DMA_FROM_DEVICE);
+               dma_unmap_unaligned(virt_to_phys(desc), sizeof(*desc),
+                                   DMA_FROM_DEVICE);
                if (desc->ctrl & QDMA_DESC_DONE_MASK)
                        break;
 
@@ -864,8 +885,8 @@ static int airoha_eth_recv(struct udevice *dev, int flags, 
uchar **packetp)
        q = &qdma->q_rx[qid];
        desc = &q->desc[q->head];
 
-       dma_unmap_single(virt_to_phys(desc), sizeof(*desc),
-                        DMA_FROM_DEVICE);
+       dma_unmap_unaligned(virt_to_phys(desc), sizeof(*desc),
+                           DMA_FROM_DEVICE);
 
        if (!(desc->ctrl & QDMA_DESC_DONE_MASK))
                return -EAGAIN;
-- 
2.47.2

Reply via email to