From 189d0b4477077f0445772d7899d8bd0a17e1f67a Mon Sep 17 00:00:00 2001 From: Mikhail Kshevetskiy Date: Wed, 9 Jul 2025 12:28:07 +0300 Subject: [PATCH 1/4] drivers/net/airoha_eth: add missing terminator for compatible devices list Compatible device list must have a terminator. If terminator is missed the u-boot driver subsystem will access random data placed after the list in the memory. The issue can be observed with the "dm compat" command. Signed-off-by: Mikhail Kshevetskiy --- drivers/net/airoha_eth.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/airoha_eth.c b/drivers/net/airoha_eth.c index 7e35e1fd41d..2aa42b3d727 100644 --- a/drivers/net/airoha_eth.c +++ b/drivers/net/airoha_eth.c @@ -926,6 +926,7 @@ static int arht_eth_write_hwaddr(struct udevice *dev) static const struct udevice_id airoha_eth_ids[] = { { .compatible = "airoha,en7581-eth" }, + { } }; static const struct eth_ops airoha_eth_ops = { From 5d49fa9e56bb0e0b085f5c794ba508349385f6b8 Mon Sep 17 00:00:00 2001 From: Mikhail Kshevetskiy Date: Wed, 9 Jul 2025 12:28:08 +0300 Subject: [PATCH 2/4] drivers/net/airoha_eth: fix packet transmission errors 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 --- 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; From 997786bbf473b3567bea5fa7ead21a8a9299c2ee Mon Sep 17 00:00:00 2001 From: Mikhail Kshevetskiy Date: Wed, 9 Jul 2025 12:28:09 +0300 Subject: [PATCH 3/4] drivers/net/airoha_eth: fix stalling in package receiving ARCH_DMA_MINALIGN is 64 for ARMv7a/ARMv8a architectures, but RX/TX descriptors are 32 bytes long. So they may not be aligned on an ARCH_DMA_MINALIGN boundary. In case of RX path, this may cause the following problem 1) Assume that a packet has arrived and the EVEN rx descriptor has been updated with the incoming data. The driver will invalidate and check the corresponding rx descriptor. 2) Now suppose the next descriptor (ODD) has not yet completed. Please note that all even descriptors starts on 64-byte boundary, and the odd ones are NOT aligned on 64-byte boundary. Inspecting even descriptor, we will read the entire CPU cache line (64 bytes). So we read and sore in CPU cache also the next (odd) descriptor. 3) Now suppose the next packet (for the odd rx descriptor) arrived while the first packet was being processed. So we have new data in memory but old data in cache. 4) After packet processing (in arht_eth_free_pkt() function) we will cleanup the descriptor and put it back to rx queue. This will call flush_dcache_range() function for the even descriptor, so the odd one will be flushed as well (it is in the same cache line). So the old data will be written to the next rx descriptor. 5) We get a freeze. The next descriptor is empty (so the driver is waiting for packets), but the hardware will continue to receive packets on other available descriptors. This will continue until the last available rx descriptor is full. Then the hardware will also freeze. The problem will be solved if the previous descriptor will be put back to the queue instead of the current one. If the current descriptor is even (starts on a 64-byte boundary), then putting the previous descriptor to the rx queue will affect the previous cache line. To be 100% ok, we must make sure that the previous and the one before the previous descriptor cannot be used for receiving at this moment. If the current descriptor is odd, then the previous descriptor is on the same cache line. Both (current and previous) descriptors are not currently in use, so issue will not arrise. WARNING: The following restrictions on PKTBUFSRX must be held: * PKTBUFSRX is even, * PKTBUFSRX >= 4. 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 --- drivers/net/airoha_eth.c | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/drivers/net/airoha_eth.c b/drivers/net/airoha_eth.c index e18816b2943..477a132fb1f 100644 --- a/drivers/net/airoha_eth.c +++ b/drivers/net/airoha_eth.c @@ -392,13 +392,14 @@ static int airoha_fe_init(struct airoha_eth *eth) return 0; } -static void airoha_qdma_reset_rx_desc(struct airoha_queue *q, int index, - uchar *rx_packet) +static void airoha_qdma_reset_rx_desc(struct airoha_queue *q, int index) { struct airoha_qdma_desc *desc; + uchar *rx_packet; u32 val; desc = &q->desc[index]; + rx_packet = net_rx_packets[index]; index = (index + 1) % q->ndesc; dma_map_single(rx_packet, PKTSIZE_ALIGN, DMA_TO_DEVICE); @@ -420,7 +421,7 @@ static void airoha_qdma_init_rx_desc(struct airoha_queue *q) int i; for (i = 0; i < q->ndesc; i++) - airoha_qdma_reset_rx_desc(q, i, net_rx_packets[i]); + airoha_qdma_reset_rx_desc(q, i); } static int airoha_qdma_init_rx_queue(struct airoha_queue *q, @@ -444,10 +445,14 @@ static int airoha_qdma_init_rx_queue(struct airoha_queue *q, RX_RING_SIZE_MASK, FIELD_PREP(RX_RING_SIZE_MASK, ndesc)); + /* + * See arht_eth_free_pkt() for the reasons used to fill + * REG_RX_CPU_IDX(qid) register. + */ airoha_qdma_rmw(qdma, REG_RX_RING_SIZE(qid), RX_RING_THR_MASK, FIELD_PREP(RX_RING_THR_MASK, 0)); airoha_qdma_rmw(qdma, REG_RX_CPU_IDX(qid), RX_RING_CPU_IDX_MASK, - FIELD_PREP(RX_RING_CPU_IDX_MASK, q->ndesc - 1)); + FIELD_PREP(RX_RING_CPU_IDX_MASK, q->ndesc - 3)); airoha_qdma_rmw(qdma, REG_RX_DMA_IDX(qid), RX_RING_DMA_IDX_MASK, FIELD_PREP(RX_RING_DMA_IDX_MASK, q->head)); @@ -906,6 +911,7 @@ static int arht_eth_free_pkt(struct udevice *dev, uchar *packet, int length) struct airoha_qdma *qdma = ð->qdma[0]; struct airoha_queue *q; int qid; + u16 prev, pprev; if (!packet) return 0; @@ -913,14 +919,25 @@ static int arht_eth_free_pkt(struct udevice *dev, uchar *packet, int length) qid = 0; q = &qdma->q_rx[qid]; - dma_map_single(packet, length, DMA_TO_DEVICE); - - airoha_qdma_reset_rx_desc(q, q->head, packet); - - airoha_qdma_rmw(qdma, REG_RX_CPU_IDX(qid), RX_RING_CPU_IDX_MASK, - FIELD_PREP(RX_RING_CPU_IDX_MASK, q->head)); + /* + * Due to cpu cache issue the airoha_qdma_reset_rx_desc() function + * will always touch 2 descriptors: + * - if current descriptor is even, then the previous and the one + * before previous descriptors will be touched (previous cacheline) + * - if current descriptor is odd, then only current and previous + * descriptors will be touched (current cacheline) + * + * Thus, to prevent possible destroying of rx queue, only (q->ndesc - 2) + * descriptors might be used for packet receiving. + */ + prev = (q->head + q->ndesc - 1) % q->ndesc; + pprev = (q->head + q->ndesc - 2) % q->ndesc; q->head = (q->head + 1) % q->ndesc; + airoha_qdma_reset_rx_desc(q, prev); + airoha_qdma_rmw(qdma, REG_RX_CPU_IDX(qid), RX_RING_CPU_IDX_MASK, + FIELD_PREP(RX_RING_CPU_IDX_MASK, pprev)); + return 0; } From 0e59a2ca9d29da7e87de2089bbeb77cdc9e927b2 Mon Sep 17 00:00:00 2001 From: Mikhail Kshevetskiy Date: Wed, 9 Jul 2025 12:28:10 +0300 Subject: [PATCH 4/4] drivers/net/airoha_eth: enable hw padding of short tx packets Transmission of short packets does not work good for XFI (GDM2) and HSGMII (GDM3) interfaces. The issue can be solved with: - padding of short packets to 60 bytes - setting of PAD_EN bit in the corresponding REG_GDM_FWD_CFG(n) register. The issue should present for the lan switch (GDM1) as well, but it does does not appear due to unknown reason. This patch set PAD_EN bit for the used GDM. Signed-off-by: Mikhail Kshevetskiy --- drivers/net/airoha_eth.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/net/airoha_eth.c b/drivers/net/airoha_eth.c index 477a132fb1f..6588eb3a806 100644 --- a/drivers/net/airoha_eth.c +++ b/drivers/net/airoha_eth.c @@ -97,6 +97,7 @@ (_n) == 2 ? GDM2_BASE : GDM1_BASE) #define REG_GDM_FWD_CFG(_n) GDM_BASE(_n) +#define GDM_PAD_EN BIT(28) #define GDM_DROP_CRC_ERR BIT(23) #define GDM_IP4_CKSUM BIT(22) #define GDM_TCP_CKSUM BIT(21) @@ -380,8 +381,11 @@ static void airoha_fe_maccr_init(struct airoha_eth *eth) int p; for (p = 1; p <= ARRAY_SIZE(eth->ports); p++) { - /* Disable any kind of CRC drop or offload */ - airoha_fe_wr(eth, REG_GDM_FWD_CFG(p), 0); + /* + * Disable any kind of CRC drop or offload. + * Enable padding of short TX packets to 60 bytes. + */ + airoha_fe_wr(eth, REG_GDM_FWD_CFG(p), GDM_PAD_EN); } } @@ -830,6 +834,11 @@ static int airoha_eth_send(struct udevice *dev, void *packet, int length) u32 val; int i; + /* + * There is no need to pad short TX packets to 60 bytes since the + * GDM_PAD_EN bit set in the corresponding REG_GDM_FWD_CFG(n) register. + */ + dma_addr = dma_map_single(packet, length, DMA_TO_DEVICE); qid = 0;