Thank you for your contribution! Sashiko AI review found 1 potential issue(s) 
to consider:
- [High] drm/vc4: dsi: race on reg_dma_mem before disable_irq() in 
vc4_dsi_host_transfer()
--

commit 748a80df1fc43dd3d1d753f275003873696caaaf
Author: Henry Zhang <[email protected]>

drm/vc4: dsi: Recover from a timed-out transfer

This commit recovers the VC4 DSI block after a transfer timeout by resetting
the DSI controller and its analog front end, then restoring the hardware
configuration from pre_enable while holding the interrupt disabled.

> diff --git a/drivers/gpu/drm/vc4/vc4_dsi.c b/drivers/gpu/drm/vc4/vc4_dsi.c
> index 3ee6e8e86a147..6edda74e1117c 100644
> --- a/drivers/gpu/drm/vc4/vc4_dsi.c
> +++ b/drivers/gpu/drm/vc4/vc4_dsi.c

[ ... ]

> @@ -1374,6 +1378,120 @@ static ssize_t vc4_dsi_host_transfer(struct 
> mipi_dsi_host *host,
>       return ret;
>  }
>  
> +/*
> + * Reset the DSI block and its analog front end, then restore the
> + * configuration done in pre_enable. Resetting the FIFOs alone does not
> + * clear the state the block can be left in after a transfer timed out
> + * (e.g. on an LP receive timeout): every following transfer then times
> + * out as well, until the next modeset.
> + */
> +static void vc4_dsi_reset_and_restore(struct vc4_dsi *dsi)
> +{
> +     u32 en = dsi->variant->port ? DSI1_CTRL_EN : DSI0_CTRL_CTRL0;
> +     u32 ctrl = DSI_PORT_READ(CTRL);

[ ... ]

> +     /* Ungate the block. */
> +     DSI_PORT_WRITE(CTRL, ctrl);
> +     /* Bring AFE out of reset. */
> +     DSI_PORT_WRITE(PHY_AFEC0, afec0 & ~DSI_PORT_BIT(PHY_AFEC0_RESET));
> +     DSI_PORT_WRITE(DISP0_CTRL, disp0);
> +
> +     DSI_PORT_WRITE(INT_STAT, DSI_PORT_READ(INT_STAT));
> +     DSI_PORT_WRITE(INT_EN, DSI_PORT_BIT(INTERRUPTS_ALWAYS_ENABLED));
> +}
> +
> +static ssize_t vc4_dsi_host_transfer(struct mipi_dsi_host *host,
> +                                  const struct mipi_dsi_msg *msg)
> +{
> +     struct vc4_dsi *dsi = host_to_dsi(host);
> +     u32 disp0_ctrl;
> +     ssize_t ret;
> +
> +     guard(mutex)(&dsi->transfer_lock);
> +
> +     ret = vc4_dsi_transfer(dsi, msg);
> +     /* Only a running block can be reset and restored. */
> +     if (ret != -ETIMEDOUT || !dsi->enabled)
> +             return ret;
> +
> +     /*
> +      * Leave the block usable for the next transfer. The message is not
> +      * resent: it may already have reached the peripheral, and only the
> +      * caller knows whether it is safe to repeat.
> +      */
> +     disp0_ctrl = DSI_PORT_READ(DISP0_CTRL);
> +     dev_warn(&dsi->pdev->dev,
> +              "transfer timed out (stat 0x%08x), resetting DSI\n",
> +              DSI_PORT_READ(STAT));
> +     /*
> +      * Keep the interrupt handler out until the block is restored and
> +      * INT_STAT is cleared. The error interrupts are still enabled, and
> +      * a handler running during the reset could complete the next
> +      * transfer, or race with the register writes on DSI1 of the
> +      * BCM2835, where both go through the DMA workaround. disable_irq()
> +      * also waits for a late completion interrupt of the timed-out
> +      * transfer.
> +      */
> +     disable_irq(dsi->irq);
> +     vc4_dsi_reset_and_restore(dsi);
> +     DSI_PORT_WRITE(DISP0_CTRL, disp0_ctrl);
> +     enable_irq(dsi->irq);

[Severity: High]
Can a late or error interrupt race on dsi->reg_dma_mem in vc4_dsi_transfer()
before disable_irq(dsi->irq) is reached in vc4_dsi_host_transfer(),
corrupting DSI1_CTRL before vc4_dsi_reset_and_restore() saves and restores
it?

When wait_for_completion_timeout() times out in vc4_dsi_transfer(),
dsi->irq and INT_EN are still enabled while vc4_dsi_transfer() performs
five register writes before returning -ETIMEDOUT:

drivers/gpu/drm/vc4/vc4_dsi.c:vc4_dsi_transfer() {
        ...
        DSI_PORT_WRITE(INT_EN, DSI_PORT_BIT(INTERRUPTS_ALWAYS_ENABLED));

        if (ret)
                goto reset_fifo_and_return;
        ...
reset_fifo_and_return:
        drm_err(drm, "DSI transfer failed, resetting: %d\n", ret);

        DSI_PORT_WRITE(TXPKT1C, DSI_PORT_READ(TXPKT1C) & ~DSI_TXPKT1C_CMD_EN);
        udelay(1);
        DSI_PORT_WRITE(CTRL,
                       DSI_PORT_READ(CTRL) |
                       DSI_PORT_BIT(CTRL_RESET_FIFOS));

        DSI_PORT_WRITE(TXPKT1C, 0);
        DSI_PORT_WRITE(INT_EN, DSI_PORT_BIT(INTERRUPTS_ALWAYS_ENABLED));
        return ret;
}

On BCM2835 DSI1 (where broken_axi_workaround is enabled), DSI_PORT_WRITE()
calls dsi_dma_workaround_write(), which writes the value into the single
shared buffer *dsi->reg_dma_mem and submits a DMA transfer from
dsi->reg_dma_paddr:

drivers/gpu/drm/vc4/vc4_dsi.c:dsi_dma_workaround_write() {
        ...
        *dsi->reg_dma_mem = val;

        tx = chan->device->device_prep_dma_memcpy(chan,
                                                  dsi->reg_paddr + offset,
                                                  dsi->reg_dma_paddr,
                                                  4, 0);
        ...
}

If the threaded handler vc4_dsi_irq_handler() runs concurrently (for
example on an LPRX/TA timeout error interrupt or a late completion
interrupt) and calls DSI_PORT_WRITE(INT_STAT, stat), it overwrites
*dsi->reg_dma_mem = stat without any lock.

If that happens during the read-modify-write of CTRL in
reset_fifo_and_return, the DMA transfer can write stat into DSI1_CTRL.
When vc4_dsi_reset_and_restore() then runs under disable_irq(dsi->irq), it
snapshots the corrupted register via u32 ctrl = DSI_PORT_READ(CTRL) and
writes it back at the end of the reset, leaving DSI1_CTRL corrupted (for
example with DSI1_CTRL_EN cleared so the block remains gated).

Should disable_irq(dsi->irq) be called in vc4_dsi_transfer() before the
post-timeout register writes (or before reset_fifo_and_return)?

-- 
Sashiko AI review ยท 
https://sashiko.dev/#/patchset/[email protected]?part=5

Reply via email to