net: microchip: fdma: fix DCB corruption under sustained load

__fdma_dcb_add() prepares a DCB and links it into a channel's chain by
writing its address to the NEXT_PTR of the current last DCB. The FDMA
may be processing the chain while this happens.

Before this patch, the link is written before the new DCB's own nextptr
and info fields, so the FDMA can follow it to a DCB that hasn't been fully
prepared yet. Under sustained load this stalls the FDMA.

Prepare the new DCB fully before linking it, and update the previous
DCB's NEXT_PTR using the two-step sequence from the datasheet (poison
the LSB and flush, then clear it) so the FDMA never observes a
partially-updated pointer.

Reported by a customer running iperf3 --bidir against a lan969x board.

Reproduced on lan969x with iperf3, throughput collapsing to zero:

  [  5]   1.00-2.00   sec  73.9 MBytes   620 Mbits/sec    0    305 KBytes
  [  5]   2.00-3.00   sec  73.9 MBytes   620 Mbits/sec    0    305 KBytes
  [  5]   3.00-4.00   sec  13.9 MBytes   116 Mbits/sec    2   1.41 KBytes
  [  5]   4.00-5.00   sec  0.00 Bytes  0.00 bits/sec    1   1.41 KBytes
  [  5]   5.00-6.00   sec  0.00 Bytes  0.00 bits/sec    0   1.41 KBytes
  [  5]   6.00-7.00   sec  0.00 Bytes  0.00 bits/sec    1   1.41 KBytes

The race is timing-dependent: across three reproductions the stall
appeared anywhere from ~400s to ~3000s into the run.

Fixes: 30e48a75df ("net: microchip: add FDMA library")
Signed-off-by: Daniel Machon <daniel.machon@microchip.com>
Link: https://patch.msgid.link/20261026-microchip-fdma-dcb-corruption-v1-1-36dab6caca07@microchip.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Daniel Machon 2026-10-26 14:47:05 +01:00 committed by Jakub Kicinski
parent 074caa30a6
commit 573a6d4c7a

View File

@ -43,9 +43,19 @@ int __fdma_dcb_add(struct fdma *fdma, int dcb_idx, u64 info, u64 status,
u64 *dataptr))
{
struct fdma_dcb *dcb = fdma_dcb_get(fdma, dcb_idx);
struct fdma_dcb *prev = fdma->last_dcb;
u64 nextptr;
int i, err;
/* Per the FDMA datasheet, adding a DCB to an active channel takes
* three steps:
* 1) Write NEXT_PTR of the current last DCB with the LSB forced to
* FDMA_DCB_INVALID_DATA, and flush that write.
* 2) Clear the LSB to complete the link.
* 3) Reload the channel (FDMA_CH_RELOAD); this is done by the caller
* once the chain has been extended.
*/
for (i = 0; i < fdma->n_dbs; i++) {
err = __fdma_db_add(fdma, dcb_idx, i, status, db_cb);
if (unlikely(err))
@ -56,13 +66,25 @@ int __fdma_dcb_add(struct fdma *fdma, int dcb_idx, u64 info, u64 status,
if (unlikely(err))
return err;
fdma->last_dcb->nextptr = cpu_to_le64(nextptr);
fdma->last_dcb = dcb;
dcb->nextptr = cpu_to_le64(FDMA_DCB_INVALID_DATA);
dcb->info = cpu_to_le64(info);
/* On the first DCB of fdma_dcbs_init() prev is the DCB itself;
* don't link it to itself.
*/
if (prev != dcb) {
/* Write NEXT_PTR of the current last DCB with the LSB forced
* to FDMA_DCB_INVALID_DATA, and flush that write.
*/
prev->nextptr = cpu_to_le64(nextptr | FDMA_DCB_INVALID_DATA);
dma_wmb();
/* Clear the LSB to complete the link. */
prev->nextptr = cpu_to_le64(nextptr);
}
fdma->last_dcb = dcb;
return 0;
}
EXPORT_SYMBOL_GPL(__fdma_dcb_add);