From 8eae6d950df532b98fa4fc9eb0a5754ca99c75f5 Mon Sep 17 00:00:00 2001 From: "Daniel P. Carvalho" Date: Sun, 4 Oct 2026 00:54:54 -0300 Subject: [PATCH] arch/mips/pic32mz: fix the Ethernet D-Cache coherency. VIRT_ADDR() converted the DMA buffer addresses to KSEG1, while the buffers come from g_buffers, which is linked in KSEG0 when the data memory is cached. Buffers then ended up in the free list under both aliases. Use the segment g_buffers is linked in instead. A buffer handed to an RX descriptor may still have dirty D-Cache lines, at least the free list link written into it. If such a line is evicted while the DMA writes the frame, it overwrites part of the frame. Discard the buffer from the D-Cache before giving it to the DMA. Assisted-by: Claude Code:claude-opus-5-5 Signed-off-by: Daniel P. Carvalho --- arch/mips/src/pic32mz/pic32mz_ethernet.c | 41 ++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/arch/mips/src/pic32mz/pic32mz_ethernet.c b/arch/mips/src/pic32mz/pic32mz_ethernet.c index 7d5b7004d0b..c2358940f27 100644 --- a/arch/mips/src/pic32mz/pic32mz_ethernet.c +++ b/arch/mips/src/pic32mz/pic32mz_ethernet.c @@ -334,7 +334,11 @@ /* Misc Helper Macros *******************************************************/ #define PHYS_ADDR(va) ((uint32_t)(va) & 0x1fffffff) -#define VIRT_ADDR(pa) (KSEG1_BASE | (uint32_t)(pa)) +/* Buffers are always accessed through the segment g_buffers is linked in + * (KSEG0 or KSEG1), so that no buffer is ever reached through two aliases. + */ + +#define VIRT_ADDR(pa) (((uint32_t)g_buffers & 0xe0000000) | (uint32_t)(pa)) /**************************************************************************** * Private Types @@ -445,6 +449,7 @@ static void pic32mz_dumprxdesc(struct pic32mz_rxdesc_s *rxdesc, static inline void pic32mz_bufferinit(struct pic32mz_driver_s *priv); static uint8_t *pic32mz_allocbuffer(struct pic32mz_driver_s *priv); +static uint8_t *pic32mz_rxbuffer(struct pic32mz_driver_s *priv); static void pic32mz_freebuffer(struct pic32mz_driver_s *priv, uint8_t *buffer); @@ -768,6 +773,36 @@ static uint8_t *pic32mz_allocbuffer(struct pic32mz_driver_s *priv) return (uint8_t *)sq_remfirst(&priv->pd_freebuffers); } +/**************************************************************************** + * Function: pic32mz_rxbuffer + * + * Description: + * Allocate one buffer for an RX descriptor. The free list link and any + * data left by the network stack may still sit in dirty D-Cache lines; + * they are discarded so that a later eviction cannot overwrite the frame + * written by the DMA. + * + * Input Parameters: + * priv - Pointer to EMAC device driver structure + * + * Returned Value: + * Pointer to the allocated buffer (or NULL on failure) + * + ****************************************************************************/ + +static uint8_t *pic32mz_rxbuffer(struct pic32mz_driver_s *priv) +{ + uint8_t *buffer = pic32mz_allocbuffer(priv); + + if (buffer != NULL) + { + up_invalidate_dcache((uintptr_t)buffer, + (uintptr_t)buffer + PIC32MZ_ALIGNED_BUFSIZE); + } + + return buffer; +} + /**************************************************************************** * Function: pic32mz_freebuffer * @@ -905,7 +940,7 @@ static inline void pic32mz_rxdescinit(struct pic32mz_driver_s *priv) rxdesc->rsv1 = 0; rxdesc->rsv2 = 0; - rxdesc->address = PHYS_ADDR(pic32mz_allocbuffer(priv)); + rxdesc->address = PHYS_ADDR(pic32mz_rxbuffer(priv)); rxdesc->status = RXDESC_STATUS_EOWN | RXDESC_STATUS_NPV; /* Set the NEXTED pointer. If this is the last descriptor in the @@ -1514,7 +1549,7 @@ static void pic32mz_rxdone(struct pic32mz_driver_s *priv) * descriptor back to the hardware with its current buffer. */ - rxbuffer = pic32mz_allocbuffer(priv); + rxbuffer = pic32mz_rxbuffer(priv); if (rxbuffer == NULL) { nwarn("WARNING: No free buffer, packet dropped\n");