On Wed, Nov 11, 2026 at 03:16:33PM -0700, James Hilliard wrote:
> serial8250_release_dma() ignores the return from dmaengine_terminate_sync()
> before freeing the coherent RX buffer, unmapping the TX FIFO and releasing
> the channels. If the provider fails to stop a channel, the DMAengine
> wrapper returns the error without synchronizing its callbacks. Hardware
> or a detached callback can then access the released storage and state.
> 
> Checking the error and returning early would not preserve their lifetime:
> serial core frees the TX FIFO after the driver's shutdown callback returns,
> and port removal can then release the callback context as well.
> 
> Require successful synchronous termination of both channels before
> releasing any DMA resources. Retry failed channels after a sleep outside
> the port lock, leaving channels that have already stopped and drained
> alone. Keep the shutdown gate and storage reservation in place throughout
> the retries.
> 
> Start retries with a 1 ms sleep and double it up to one second. This
> makes short-lived failures responsive while bounding sustained retry
> activity; neither delay is a hardware timeout. Report the initial failure
> and ongoing failures with the elapsed time and current RX/TX error codes.
> Track the five-second progress-report interval separately for each
> teardown, so failures on different ports are reported independently.
> Report recovery once both channels have stopped and drained.
> 
> A permanently failed DMA engine blocks close or removal. This deliberately
> retains the buffers, mappings, channels and callback context until the
> provider can establish that they are no longer in use.

...

> +static void serial8250_terminate_dma(struct uart_8250_port *p)
> +{
> +     struct uart_8250_dma *dma = p->dma;
> +     unsigned int delay_us = 1 * USEC_PER_MSEC;
> +     ktime_t start, last_report;
> +     int rx_ret, tx_ret;

> +     rx_ret = dmaengine_terminate_sync(dma->rxchan);
> +     tx_ret = dmaengine_terminate_sync(dma->txchan);
> +     if (!rx_ret && !tx_ret)
> +             return;

Why not check these already in the loop? See below.

> +     start = ktime_get();
> +     last_report = start;
> +     dev_err(p->port.dev,
> +             "DMA termination failed: RX %d, TX %d; teardown blocked\n",
> +             rx_ret, tx_ret);
> +
> +     /*
> +      * Shutdown cannot return while DMA owns storage or callback state:
> +      * serial core frees the TX FIFO afterwards, and removal frees the port.
> +      * Retain both channels and retry outside the port lock. A permanently
> +      * failed DMA engine therefore prevents teardown from completing.
> +      */
> +     while (rx_ret || tx_ret) {

I believe with refactoring this to do {} while () we may deduplicate
the dmaengine_terminate_sync() calls.

> +             fsleep(delay_us);
> +             /* Retry promptly at first, then limit sustained retry 
> activity. */
> +             delay_us = min(delay_us * 2, 1 * USEC_PER_SEC);
> +             if (rx_ret)
> +                     rx_ret = dmaengine_terminate_sync(dma->rxchan);
> +             if (tx_ret)
> +                     tx_ret = dmaengine_terminate_sync(dma->txchan);
> +             if (rx_ret || tx_ret) {
> +                     ktime_t now = ktime_get();
> +
> +                     if (ktime_ms_delta(now, last_report) < 5 * MSEC_PER_SEC)
> +                             continue;
> +                     dev_err(p->port.dev,
> +                             "DMA termination still failing after %lld ms: 
> RX %d, TX %d\n",
> +                             ktime_ms_delta(now, start), rx_ret, tx_ret);
> +                     last_report = now;
> +             }
> +     }
> +     dev_info(p->port.dev, "DMA termination recovered after %lld ms\n",
> +              ktime_ms_delta(ktime_get(), start));
> +}

-- 
With Best Regards,
Andy Shevchenko



Reply via email to