[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <6894b54e-651f-1caf-d363-79d1ef0eee14@st.com>
Date: Fri, 26 Apr 2019 15:41:08 +0200
From: Arnaud Pouliquen <arnaud.pouliquen@...com>
To: Vinod Koul <vkoul@...nel.org>
CC: Dan Williams <dan.j.williams@...el.com>,
Pierre-Yves MORDRET <pierre-yves.mordret@...com>,
<linux-stm32@...md-mailman.stormreply.com>,
<linux-kernel@...r.kernel.org>, <dmaengine@...r.kernel.org>
Subject: Re: [PATCH] dmaengine: stm32-dma: fix residue calculation in
stm32-dma
Hi Vinod
On 4/26/19 2:17 PM, Vinod Koul wrote:
> Hi Arnaud,
>
> Sorry for delay in review, the conference travel/vacation plan delayed
> this.
no problem, just a rememder to be sure that you not missed it in the
patch stream.
>
> On 27-03-19, 13:21, Arnaud Pouliquen wrote:
>> During residue calculation. the DMA can switch to the next sg. When
>> this race condition occurs, the residue returned value is not valid.
>> Indeed the position in the sg returned by the hardware is the position
>> of the next sg, not the current sg.
>> Solution is to check the sg after the calculation to verify it.
>> If a transition is detected we consider that the DMA has switched to
>> the beginning of next sg.
>
> Now, that sounds like duct tape. Why should we bother doing that.
>
> Also looking back at the stm32_dma_desc_residue() and calls to it from
> stm32_dma_tx_status() am not sure we are doing the right thing
Please, could you explain what you have in mind here?
>
> why are we looking at next_sg here, can you explain me that please
This solution is similar to one implemented in the at_hdmac.c driver
(atc_get_bytes_left function).
Yes could be consider as a workaround for a hardware issue...
In stm32 DMA Peripheral, we can register up to 2 sg descriptors (sg1 &
sg2)in DMA registers, and use it in a cyclic mode (auto reload). This
mode is mainly use for audio transfer initiated by an ALSA driver.
>From hardware point of view the DMA transfers first block based on sg1,
then it updates registers to prepare sg2 transfer, and then generates an
IRQ to inform that it issues the next transfer (sg2).
Then driver can update sg1 to prepare the third transfer...
In parallel the client driver can requests status to get the residue to
update internal pointer.
The issue is in the race condition between the call of the
device_tx_status ops and the update of the DMA register on sg switch.
During a short time the hardware updated the registers containing the
sg ID but not the transfer counter(SxNDTR). In this case there is a
mismatch between the Sg ID and the associated transfer counter.
So residue calculation is wrong.
Idea of this patch is to perform the calculation and then to crosscheck
that the hardware has not switched to the next sg during the
calculation. The way to crosscheck is to compare the the sg ID before
and after the calculation.
I tested the solution to force a new recalculation but no real solution
to trust the registers during this phase. In this case an approximation
is to consider that the DMA is transferring the first bytes of the next sg.
So we return the residue corresponding to the beginning of the next buffer.
Don't hesitate if it is still not clear
Thanks
Arnaud
>
>>
>> Signed-off-by: Arnaud Pouliquen <arnaud.pouliquen@...com>
>> Signed-off-by: Pierre-Yves MORDRET <pierre-yves.mordret@...com>
>> ---
>> drivers/dma/stm32-dma.c | 70 ++++++++++++++++++++++++++++++++++++++++---------
>> 1 file changed, 57 insertions(+), 13 deletions(-)
>>
>> diff --git a/drivers/dma/stm32-dma.c b/drivers/dma/stm32-dma.c
>> index 4903a40..30309d2 100644
>> --- a/drivers/dma/stm32-dma.c
>> +++ b/drivers/dma/stm32-dma.c
>> @@ -1038,33 +1038,77 @@ static u32 stm32_dma_get_remaining_bytes(struct stm32_dma_chan *chan)
>> return ndtr << width;
>> }
>>
>> +static bool stm32_dma_is_current_sg(struct stm32_dma_chan *chan)
>> +{
>> + struct stm32_dma_device *dmadev = stm32_dma_get_dev(chan);
>> + struct stm32_dma_sg_req *sg_req;
>> + u32 dma_scr, dma_smar, id;
>> +
>> + id = chan->id;
>> + dma_scr = stm32_dma_read(dmadev, STM32_DMA_SCR(id));
>> +
>> + if (!(dma_scr & STM32_DMA_SCR_DBM))
>> + return true;
>> +
>> + sg_req = &chan->desc->sg_req[chan->next_sg];
>> +
>> + if (dma_scr & STM32_DMA_SCR_CT) {
>> + dma_smar = stm32_dma_read(dmadev, STM32_DMA_SM0AR(id));
>> + return (dma_smar == sg_req->chan_reg.dma_sm0ar);
>> + }
>> +
>> + dma_smar = stm32_dma_read(dmadev, STM32_DMA_SM1AR(id));
>> +
>> + return (dma_smar == sg_req->chan_reg.dma_sm1ar);
>> +}
>> +
>> static size_t stm32_dma_desc_residue(struct stm32_dma_chan *chan,
>> struct stm32_dma_desc *desc,
>> u32 next_sg)
>> {
>> u32 modulo, burst_size;
>> - u32 residue = 0;
>> + u32 residue;
>> + u32 n_sg = next_sg;
>> + struct stm32_dma_sg_req *sg_req = &chan->desc->sg_req[chan->next_sg];
>> int i;
>>
>> + residue = stm32_dma_get_remaining_bytes(chan);
>> +
>> /*
>> - * In cyclic mode, for the last period, residue = remaining bytes from
>> - * NDTR
>> + * Calculate the residue means compute the descriptors
>> + * information:
>> + * - the sg currently transferred
>> + * - the remaining position in this sg (NDTR).
>> + *
>> + * The issue is that a race condition can occur if DMA is
>> + * running. DMA can have started to transfer the next sg before
>> + * the position in sg is read. In this case the remaing position
>> + * can correspond to the new sg position.
>> + * The strategy implemented in the stm32 driver is to check the
>> + * sg transition. If detected we can not trust the SxNDTR register
>> + * value, this register can not be up to date during the transition.
>> + * In this case we can assume that the dma is at the beginning of next
>> + * sg so we calculate the residue in consequence.
>> */
>> - if (chan->desc->cyclic && next_sg == 0) {
>> - residue = stm32_dma_get_remaining_bytes(chan);
>> - goto end;
>> +
>> + if (!stm32_dma_is_current_sg(chan)) {
>> + n_sg++;
>> + if (n_sg == chan->desc->num_sgs)
>> + n_sg = 0;
>> + residue = sg_req->len;
>> }
>>
>> /*
>> - * For all other periods in cyclic mode, and in sg mode,
>> - * residue = remaining bytes from NDTR + remaining periods/sg to be
>> - * transferred
>> + * In cyclic mode, for the last period, residue = remaining bytes
>> + * from NDTR,
>> + * else for all other periods in cyclic mode, and in sg mode,
>> + * residue = remaining bytes from NDTR + remaining
>> + * periods/sg to be transferred
>> */
>> - for (i = next_sg; i < desc->num_sgs; i++)
>> - residue += desc->sg_req[i].len;
>> - residue += stm32_dma_get_remaining_bytes(chan);
>> + if (!chan->desc->cyclic || n_sg != 0)
>> + for (i = n_sg; i < desc->num_sgs; i++)
>> + residue += desc->sg_req[i].len;
>>
>> -end:
>> if (!chan->mem_burst)
>> return residue;
>>
>> --
>> 2.7.4
>
Powered by blists - more mailing lists