Re: [PATCH] mmc: dw_mmc: handle data blocks > than 4kB if IDMAC is used

From: Jaehoon Chung
Date: Wed Jul 08 2015 - 00:14:31 EST


Hi, Alexey.

On 06/25/2015 05:25 PM, Alexey Brodkin wrote:
> As per DW MobileStorage databook "each descriptor can transfer up to 4kB
> of data in chained mode", moreover buffer size that is put in "des1" is
> limited to 13 bits, i.e. for example on attempt to
> IDMAC_SET_BUFFER1_SIZE(desc, 8192) size value that's effectively written
> will be 0.
>
> On the platform with 8kB PAGE_SIZE I see dw_mmc gets data blocks in
> SG-list of 8kB size and that leads to unpredictable behavior of the
> SD/MMC controller.

I didn't see your problem, since i didn't test with 8K PAGE_SIZE.
But I think your patch is reasonable.
As possible, I want to know in more detail what unpredictable behavior.
(Just stuck behavior?)

>
> In particular on write to FAT partition of SD-card the controller will
> stuck in the middle of DMA transaction.
>
> Solution to the problem is simple - we need to pass large (> 4kB) data
> buffers to the controller via multiple descriptors. And that's what
> that change does.
>
> What's interesting I did try original driver on same platform but
> configured with 4kB PAGE_SIZE and may confirm that data blocks passed
> in SG-list to dw_mmc never exeed 4kB limit - that explains why nobody
> ever faced a problem I did.
>
> Signed-off-by: Alexey Brodkin <abrodkin@xxxxxxxxxxxx>
> Cc: Seungwon Jeon <tgih.jun@xxxxxxxxxxx>
> Cc: Jaehoon Chung <jh80.chung@xxxxxxxxxxx>
> Cc: Ulf Hansson <ulf.hansson@xxxxxxxxxx>
> Cc: arc-linux-dev@xxxxxxxxxxxx
> Cc: linux-kernel@xxxxxxxxxxxxxxx
> ---
> drivers/mmc/host/dw_mmc.c | 109 ++++++++++++++++++++++++++++++----------------
> 1 file changed, 71 insertions(+), 38 deletions(-)
>
> diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c
> index 40e9d8e..e41fb74 100644
> --- a/drivers/mmc/host/dw_mmc.c
> +++ b/drivers/mmc/host/dw_mmc.c
> @@ -99,6 +99,9 @@ struct idmac_desc {
>
> __le32 des3; /* buffer 2 physical address */
> };
> +
> +/* Each descriptor can transfer up to 4KB of data in chained mode */
> +#define DW_MCI_DESC_DATA_LENGTH 0x1000
> #endif /* CONFIG_MMC_DW_IDMAC */
>
> static bool dw_mci_reset(struct dw_mci *host);
> @@ -462,66 +465,96 @@ static void dw_mci_idmac_complete_dma(struct dw_mci *host)
> static void dw_mci_translate_sglist(struct dw_mci *host, struct mmc_data *data,
> unsigned int sg_len)
> {
> + unsigned int desc_len;
> int i;
> if (host->dma_64bit_address == 1) {
> - struct idmac_desc_64addr *desc = host->sg_cpu;
> + struct idmac_desc_64addr *desc_first, *desc_last, *desc;

Why it needs desc_first?

> +
> + desc_first = desc_last = desc = host->sg_cpu;
>
> - for (i = 0; i < sg_len; i++, desc++) {
> + for (i = 0; i < sg_len; i++) {
> unsigned int length = sg_dma_len(&data->sg[i]);
> u64 mem_addr = sg_dma_address(&data->sg[i]);
>
> - /*
> - * Set the OWN bit and disable interrupts for this
> - * descriptor
> - */
> - desc->des0 = IDMAC_DES0_OWN | IDMAC_DES0_DIC |
> - IDMAC_DES0_CH;
> - /* Buffer length */
> - IDMAC_64ADDR_SET_BUFFER1_SIZE(desc, length);
> -
> - /* Physical address to DMA to/from */
> - desc->des4 = mem_addr & 0xffffffff;
> - desc->des5 = mem_addr >> 32;
> + for ( ; length ; desc++) {

Is there no infinite loop case?

Best Regards,
Jaehoon Chung

> + desc_len = (length <= DW_MCI_DESC_DATA_LENGTH) ?
> + length : DW_MCI_DESC_DATA_LENGTH;
> +
> + length -= desc_len;
> +
> + /*
> + * Set the OWN bit and disable interrupts
> + * for this descriptor
> + */
> + desc->des0 = IDMAC_DES0_OWN | IDMAC_DES0_DIC |
> + IDMAC_DES0_CH;
> +
> + /* Buffer length */
> + IDMAC_64ADDR_SET_BUFFER1_SIZE(desc, desc_len);
> +
> + /* Physical address to DMA to/from */
> + desc->des4 = mem_addr & 0xffffffff;
> + desc->des5 = mem_addr >> 32;
> +
> + /* Update physical address for the next desc */
> + mem_addr += desc_len;
> +
> + /* Save pointer to the last descriptor */
> + desc_last = desc;
> + }
> }
>
> /* Set first descriptor */
> - desc = host->sg_cpu;
> - desc->des0 |= IDMAC_DES0_FD;
> + desc_first->des0 |= IDMAC_DES0_FD;
>
> /* Set last descriptor */
> - desc = host->sg_cpu + (i - 1) *
> - sizeof(struct idmac_desc_64addr);
> - desc->des0 &= ~(IDMAC_DES0_CH | IDMAC_DES0_DIC);
> - desc->des0 |= IDMAC_DES0_LD;
> + desc_last->des0 &= ~(IDMAC_DES0_CH | IDMAC_DES0_DIC);
> + desc_last->des0 |= IDMAC_DES0_LD;
>
> } else {
> - struct idmac_desc *desc = host->sg_cpu;
> + struct idmac_desc *desc_first, *desc_last, *desc;
> +
> + desc_first = desc_last = desc = host->sg_cpu;
>
> - for (i = 0; i < sg_len; i++, desc++) {
> + for (i = 0; i < sg_len; i++) {
> unsigned int length = sg_dma_len(&data->sg[i]);
> u32 mem_addr = sg_dma_address(&data->sg[i]);
>
> - /*
> - * Set the OWN bit and disable interrupts for this
> - * descriptor
> - */
> - desc->des0 = cpu_to_le32(IDMAC_DES0_OWN |
> - IDMAC_DES0_DIC | IDMAC_DES0_CH);
> - /* Buffer length */
> - IDMAC_SET_BUFFER1_SIZE(desc, length);
> + for ( ; length ; desc++) {
> + desc_len = (length <= DW_MCI_DESC_DATA_LENGTH) ?
> + length : DW_MCI_DESC_DATA_LENGTH;
> +
> + length -= desc_len;
> +
> + /*
> + * Set the OWN bit and disable interrupts
> + * for this descriptor
> + */
> + desc->des0 = cpu_to_le32(IDMAC_DES0_OWN |
> + IDMAC_DES0_DIC |
> + IDMAC_DES0_CH);
> +
> + /* Buffer length */
> + IDMAC_SET_BUFFER1_SIZE(desc, desc_len);
>
> - /* Physical address to DMA to/from */
> - desc->des2 = cpu_to_le32(mem_addr);
> + /* Physical address to DMA to/from */
> + desc->des2 = cpu_to_le32(mem_addr);
> +
> + /* Update physical address for the next desc */
> + mem_addr += desc_len;
> +
> + /* Save pointer to the last descriptor */
> + desc_last = desc;
> + }
> }
>
> /* Set first descriptor */
> - desc = host->sg_cpu;
> - desc->des0 |= cpu_to_le32(IDMAC_DES0_FD);
> + desc_first->des0 |= cpu_to_le32(IDMAC_DES0_FD);
>
> /* Set last descriptor */
> - desc = host->sg_cpu + (i - 1) * sizeof(struct idmac_desc);
> - desc->des0 &= cpu_to_le32(~(IDMAC_DES0_CH | IDMAC_DES0_DIC));
> - desc->des0 |= cpu_to_le32(IDMAC_DES0_LD);
> + desc_last->des0 &= cpu_to_le32(~(IDMAC_DES0_CH |
> + IDMAC_DES0_DIC));
> + desc_last->des0 |= cpu_to_le32(IDMAC_DES0_LD);
> }
>
> wmb();
> @@ -2394,7 +2427,7 @@ static int dw_mci_init_slot(struct dw_mci *host, unsigned int id)
> #ifdef CONFIG_MMC_DW_IDMAC
> mmc->max_segs = host->ring_size;
> mmc->max_blk_size = 65536;
> - mmc->max_seg_size = 0x1000;
> + mmc->max_seg_size = DW_MCI_DESC_DATA_LENGTH;
> mmc->max_req_size = mmc->max_seg_size * host->ring_size;
> mmc->max_blk_count = mmc->max_req_size / 512;
> #else
>

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/