Re: [PATCH] mmc: dw_mmc: Support more time for data read timeouts

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



+ Doug, Shawn and Vincent (maybe they can also help to review/test this change).

On Fri, 27 Aug 2021 at 10:56, Mårten Lindahl <marten.lindahl@xxxxxxxx> wrote:
>
> For data read transfers a data transfer over timer (dto_timer) is
> started to make sure the data command can be completed in cases the Data
> Transfer Over (DTO) interrupt does not come. This timer was originally
> introduced as a quirk in commit 57e104864bc48 ("mmc: dw_mmc: add quirk
> for broken data transfer over scheme"), but is since a while back part
> of the running code.
>
> The dto timer picks the DATA_TIMEOUT value in the TMOUT register for its
> timeout, which will give a max timeout of approximately 84 + (10 spare)
> milliseconds on a 200MHz clock. But this register is not intended to be
> used like that, since it is a counter for data read timeouts (DRTO) and
> response timeouts (RTO), which will result in error interrupts in case
> of data read and response delays.
>
> The TMOUT register is always set with a full value for every transfer,
> which according to the manual (and with 200MHz clock) will give a full
> DRTO of:
> ((TMOUT[10:8] -1) * 0xFFFFFF + TMOUT[31:11] * 8) / 200000000 => ~587 ms
>
> But as the same register is used for the dto_timer, the dto_timer will
> always have a fixed timeout.
>
> Instead of always setting a fixed value in TMOUT register, we can use
> data->timeout_ns for the DRTO interrupts that actually matches what was
> provided per requested command. Likewise we can also use timeout_ns for
> the dto_timer, which will allow a max timeout of 587 ms, instead of the
> fixed 94 ms. Furthermore, if a data error interrupt comes, it shouldn't
> be necessary to wait for the dto_timer before we finish the command, but
> instead we can handle it in the interrupt handler.
>
> Lets fix this. In most cases data->timeout_ns values are given, but in
> case it is not given, the maximum (default) timeout for the dto_timer,
> and the DRTO, is set to approximately 587 ms.
>
> Signed-off-by: Mårten Lindahl <marten.lindahl@xxxxxxxx>

As this touches common dw_mmc code, I would appreciate some
tests/reviews to be done before I apply this. Therefore I looped in
some of the people who have been involved with dw_mmc lately.

Kind regards
Uffe


> ---
>  drivers/mmc/host/dw_mmc.c | 108 ++++++++++++++++++++++----------------
>  1 file changed, 63 insertions(+), 45 deletions(-)
>
> diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c
> index c3229d8c7041..0762d95293d4 100644
> --- a/drivers/mmc/host/dw_mmc.c
> +++ b/drivers/mmc/host/dw_mmc.c
> @@ -52,6 +52,7 @@
>
>  #define DW_MCI_FREQ_MAX        200000000       /* unit: HZ */
>  #define DW_MCI_FREQ_MIN        100000          /* unit: HZ */
> +#define DW_MCI_DATA_TMOUT_NS_MAX       587202490
>
>  #define IDMAC_INT_CLR          (SDMMC_IDMAC_INT_AI | SDMMC_IDMAC_INT_NI | \
>                                  SDMMC_IDMAC_INT_CES | SDMMC_IDMAC_INT_DU | \
> @@ -390,6 +391,23 @@ static inline void dw_mci_set_cto(struct dw_mci *host)
>         spin_unlock_irqrestore(&host->irq_lock, irqflags);
>  }
>
> +static void dw_mci_set_dto(struct dw_mci *host, u32 timeout_ns)
> +{
> +       unsigned int dto_ns;
> +       unsigned long irqflags;
> +
> +       if (!timeout_ns || timeout_ns > DW_MCI_DATA_TMOUT_NS_MAX)
> +               dto_ns = DW_MCI_DATA_TMOUT_NS_MAX;
> +       else
> +               dto_ns = timeout_ns;
> +
> +       spin_lock_irqsave(&host->irq_lock, irqflags);
> +       if (!test_bit(EVENT_DATA_COMPLETE, &host->pending_events))
> +               mod_timer(&host->dto_timer,
> +                         jiffies + nsecs_to_jiffies(dto_ns));
> +       spin_unlock_irqrestore(&host->irq_lock, irqflags);
> +}
> +
>  static void dw_mci_start_command(struct dw_mci *host,
>                                  struct mmc_command *cmd, u32 cmd_flags)
>  {
> @@ -1144,9 +1162,10 @@ static void dw_mci_submit_data(struct dw_mci *host, struct mmc_data *data)
>         host->sg = NULL;
>         host->data = data;
>
> -       if (data->flags & MMC_DATA_READ)
> +       if (data->flags & MMC_DATA_READ) {
>                 host->dir_status = DW_MCI_RECV_STATUS;
> -       else
> +               dw_mci_set_dto(host, data->timeout_ns);
> +       } else
>                 host->dir_status = DW_MCI_SEND_STATUS;
>
>         dw_mci_ctrl_thld(host, data);
> @@ -1277,6 +1296,36 @@ static void dw_mci_setup_bus(struct dw_mci_slot *slot, bool force_clkinit)
>         mci_writel(host, CTYPE, (slot->ctype << slot->id));
>  }
>
> +static void dw_mci_set_data_timeout(struct dw_mci *host, u32 timeout_ns)
> +{
> +       u32 timeout, freq_mhz, tmp, tmout;
> +
> +       if (!timeout_ns || timeout_ns > DW_MCI_DATA_TMOUT_NS_MAX) {
> +               /* Timeout (maximum) */
> +               mci_writel(host, TMOUT, 0xFFFFFFFF);
> +               return;
> +       }
> +
> +       timeout = timeout_ns / NSEC_PER_USEC;
> +       freq_mhz = host->bus_hz / NSEC_PER_MSEC;
> +
> +       /* TMOUT[7:0] (RESPONSE_TIMEOUT) */
> +       tmout = 0xFF;
> +
> +       /* TMOUT[10:8] (DATA_TIMEOUT) */
> +       tmp = ((timeout * freq_mhz) / 0xFFFFFF) + 1;
> +       tmout |= (tmp & 0x7) << 8;
> +
> +       /* TMOUT[31:11] (DATA_TIMEOUT) */
> +       tmp = ((tmp - 1) * 0xFFFFFF) / freq_mhz;
> +       tmp = (timeout - tmp) * freq_mhz / 8;
> +       tmout |= (tmp & 0x1FFFFF) << 11;
> +
> +       mci_writel(host, TMOUT, tmout);
> +       dev_dbg(host->dev, "timeout_ns: %u => TMOUT[31:8]: 0x%08x",
> +               timeout_ns, tmout);
> +}
> +
>  static void __dw_mci_start_request(struct dw_mci *host,
>                                    struct dw_mci_slot *slot,
>                                    struct mmc_command *cmd)
> @@ -1297,7 +1346,7 @@ static void __dw_mci_start_request(struct dw_mci *host,
>
>         data = cmd->data;
>         if (data) {
> -               mci_writel(host, TMOUT, 0xFFFFFFFF);
> +               dw_mci_set_data_timeout(host, data->timeout_ns);
>                 mci_writel(host, BYTCNT, data->blksz*data->blocks);
>                 mci_writel(host, BLKSIZ, data->blksz);
>         }
> @@ -1897,31 +1946,6 @@ static int dw_mci_data_complete(struct dw_mci *host, struct mmc_data *data)
>         return data->error;
>  }
>
> -static void dw_mci_set_drto(struct dw_mci *host)
> -{
> -       unsigned int drto_clks;
> -       unsigned int drto_div;
> -       unsigned int drto_ms;
> -       unsigned long irqflags;
> -
> -       drto_clks = mci_readl(host, TMOUT) >> 8;
> -       drto_div = (mci_readl(host, CLKDIV) & 0xff) * 2;
> -       if (drto_div == 0)
> -               drto_div = 1;
> -
> -       drto_ms = DIV_ROUND_UP_ULL((u64)MSEC_PER_SEC * drto_clks * drto_div,
> -                                  host->bus_hz);
> -
> -       /* add a bit spare time */
> -       drto_ms += 10;
> -
> -       spin_lock_irqsave(&host->irq_lock, irqflags);
> -       if (!test_bit(EVENT_DATA_COMPLETE, &host->pending_events))
> -               mod_timer(&host->dto_timer,
> -                         jiffies + msecs_to_jiffies(drto_ms));
> -       spin_unlock_irqrestore(&host->irq_lock, irqflags);
> -}
> -
>  static bool dw_mci_clear_pending_cmd_complete(struct dw_mci *host)
>  {
>         if (!test_bit(EVENT_CMD_COMPLETE, &host->pending_events))
> @@ -2052,15 +2076,8 @@ static void dw_mci_tasklet_func(struct tasklet_struct *t)
>                         }
>
>                         if (!test_and_clear_bit(EVENT_XFER_COMPLETE,
> -                                               &host->pending_events)) {
> -                               /*
> -                                * If all data-related interrupts don't come
> -                                * within the given time in reading data state.
> -                                */
> -                               if (host->dir_status == DW_MCI_RECV_STATUS)
> -                                       dw_mci_set_drto(host);
> +                                               &host->pending_events))
>                                 break;
> -                       }
>
>                         set_bit(EVENT_XFER_COMPLETE, &host->completed_events);
>
> @@ -2091,16 +2108,8 @@ static void dw_mci_tasklet_func(struct tasklet_struct *t)
>                         fallthrough;
>
>                 case STATE_DATA_BUSY:
> -                       if (!dw_mci_clear_pending_data_complete(host)) {
> -                               /*
> -                                * If data error interrupt comes but data over
> -                                * interrupt doesn't come within the given time.
> -                                * in reading data state.
> -                                */
> -                               if (host->dir_status == DW_MCI_RECV_STATUS)
> -                                       dw_mci_set_drto(host);
> +                       if (!dw_mci_clear_pending_data_complete(host))
>                                 break;
> -                       }
>
>                         host->data = NULL;
>                         set_bit(EVENT_DATA_COMPLETE, &host->completed_events);
> @@ -2649,12 +2658,21 @@ static irqreturn_t dw_mci_interrupt(int irq, void *dev_id)
>                 }
>
>                 if (pending & DW_MCI_DATA_ERROR_FLAGS) {
> +                       spin_lock(&host->irq_lock);
> +
> +                       del_timer(&host->dto_timer);
> +
>                         /* if there is an error report DATA_ERROR */
>                         mci_writel(host, RINTSTS, DW_MCI_DATA_ERROR_FLAGS);
>                         host->data_status = pending;
>                         smp_wmb(); /* drain writebuffer */
>                         set_bit(EVENT_DATA_ERROR, &host->pending_events);
> +
> +                       /* In case of error, we cannot expect a DTO */
> +                       set_bit(EVENT_DATA_COMPLETE, &host->pending_events);
>                         tasklet_schedule(&host->tasklet);
> +
> +                       spin_unlock(&host->irq_lock);
>                 }
>
>                 if (pending & SDMMC_INT_DATA_OVER) {
> --
> 2.20.1
>




[Index of Archives]     [Linux Memonry Technology]     [Linux USB Devel]     [Linux Media]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux