[PATCH linux-next v2 1/1] dmaengine: at_hdmac: fix residue computation

Nicolas Ferre nicolas.ferre at atmel.com
Mon Jun 29 06:38:20 PDT 2015


Le 18/06/2015 13:25, Cyrille Pitchen a écrit :
> As claimed by the programmer datasheet and confirmed by the IP designer,
> the Block Transfer Size (BTSIZE) bitfield of the Channel x Control A
> Register (CTRLAx) always refers to a number of Source Width (SRC_WIDTH)
> transfers.
> 
> Both the SRC_WIDTH and BTSIZE bitfields can be extacted from the CTRLAx
> register to compute the DMA residue. So the 'tx_width' field is useless
> and can be removed from the struct at_desc.
> 
> Before this patch, atc_prep_slave_sg() was not consistent: BTSIZE was
> correctly initialized according to the SRC_WIDTH but 'tx_width' was always
> set to reg_width, which was incorrect for MEM_TO_DEV transfers. It led to
> bad DMA residue when 'tx_width' != SRC_WIDTH.
> 
> Also the 'tx_width' field was mostly set only in the first and last
> descriptors. Depending on the kind of DMA transfer, this field remained
> uninitialized for intermediate descriptors. The accurate DMA residue was
> computed only when the currently processed descriptor was the first or the
> last of the chain. This algorithm was a little bit odd. An accurate DMA
> residue can always be computed using the SRC_WIDTH and BTSIZE bitfields
> in the CTRLAx register.
> 
> Finally, the test to check whether the currently processed descriptor is
> the last of the chain was wrong: for cyclic transfer, last_desc->lli.dscr
> is NOT equal to zero, since set_desc_eol() is never called, but logically
> equal to first_desc->txd.phys. This bug has a side effect on the
> drivers/tty/serial/atmel_serial.c driver, which uses cyclic DMA transfer
> to receive data. Since the DMA residue was wrong each time the DMA
> transfer reaches the second (and last) period of the transfer, no more
> data were received by the USART driver till the cyclic DMA transfer loops
> back to the first period.
> 
> Signed-off-by: Cyrille Pitchen <cyrille.pitchen at atmel.com>

Yes:
Acked-by: Nicolas Ferre <nicolas.ferre at atmel.com>

It turns out that some information that I gave to Torsten for his
previous patch were wrong. Thank you Cyrille for having dig this issue
and proposed a fix.

Bye,

> ---
>  drivers/dma/at_hdmac.c      | 132 +++++++++++++++++++++++++++++---------------
>  drivers/dma/at_hdmac_regs.h |   3 +-
>  2 files changed, 88 insertions(+), 47 deletions(-)
> 
> diff --git a/drivers/dma/at_hdmac.c b/drivers/dma/at_hdmac.c
> index 59892126..d3629b7 100644
> --- a/drivers/dma/at_hdmac.c
> +++ b/drivers/dma/at_hdmac.c
> @@ -48,6 +48,8 @@
>  	BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) |\
>  	BIT(DMA_SLAVE_BUSWIDTH_4_BYTES))
>  
> +#define ATC_MAX_DSCR_TRIALS	10
> +
>  /*
>   * Initial number of descriptors to allocate for each channel. This could
>   * be increased during dma usage.
> @@ -285,28 +287,19 @@ static struct at_desc *atc_get_desc_by_cookie(struct at_dma_chan *atchan,
>   *
>   * @current_len: the number of bytes left before reading CTRLA
>   * @ctrla: the value of CTRLA
> - * @desc: the descriptor containing the transfer width
>   */
> -static inline int atc_calc_bytes_left(int current_len, u32 ctrla,
> -					struct at_desc *desc)
> +static inline int atc_calc_bytes_left(int current_len, u32 ctrla)
>  {
> -	return current_len - ((ctrla & ATC_BTSIZE_MAX) << desc->tx_width);
> -}
> +	u32 btsize = (ctrla & ATC_BTSIZE_MAX);
> +	u32 src_width = ATC_REG_TO_SRC_WIDTH(ctrla);
>  
> -/**
> - * atc_calc_bytes_left_from_reg - calculates the number of bytes left according
> - * to the current value of CTRLA.
> - *
> - * @current_len: the number of bytes left before reading CTRLA
> - * @atchan: the channel to read CTRLA for
> - * @desc: the descriptor containing the transfer width
> - */
> -static inline int atc_calc_bytes_left_from_reg(int current_len,
> -			struct at_dma_chan *atchan, struct at_desc *desc)
> -{
> -	u32 ctrla = channel_readl(atchan, CTRLA);
> -
> -	return atc_calc_bytes_left(current_len, ctrla, desc);
> +	/*
> +	 * According to the datasheet, when reading the Control A Register
> +	 * (ctrla), the Buffer Transfer Size (btsize) bitfield refers to the
> +	 * number of transfers completed on the Source Interface.
> +	 * So btsize is always a number of source width transfers.
> +	 */
> +	return current_len - (btsize << src_width);
>  }
>  
>  /**
> @@ -320,7 +313,7 @@ static int atc_get_bytes_left(struct dma_chan *chan, dma_cookie_t cookie)
>  	struct at_desc *desc_first = atc_first_active(atchan);
>  	struct at_desc *desc;
>  	int ret;
> -	u32 ctrla, dscr;
> +	u32 ctrla, dscr, trials;
>  
>  	/*
>  	 * If the cookie doesn't match to the currently running transfer then
> @@ -346,15 +339,82 @@ static int atc_get_bytes_left(struct dma_chan *chan, dma_cookie_t cookie)
>  		 * the channel's DSCR register and compare it against the value
>  		 * of the hardware linked list structure of each child
>  		 * descriptor.
> +		 *
> +		 * The CTRLA register provides us with the amount of data
> +		 * already read from the source for the current child
> +		 * descriptor. So we can compute a more accurate residue by also
> +		 * removing the number of bytes corresponding to this amount of
> +		 * data.
> +		 *
> +		 * However, the DSCR and CTRLA registers cannot be read both
> +		 * atomically. Hence a race condition may occur: the first read
> +		 * register may refer to one child descriptor whereas the second
> +		 * read may refer to a later child descriptor in the list
> +		 * because of the DMA transfer progression inbetween the two
> +		 * reads.
> +		 *
> +		 * One solution could have been to pause the DMA transfer, read
> +		 * the DSCR and CTRLA then resume the DMA transfer. Nonetheless,
> +		 * this approach presents some drawbacks:
> +		 * - If the DMA transfer is paused, RX overruns or TX underruns
> +		 *   are more likey to occur depending on the system latency.
> +		 *   Taking the USART driver as an example, it uses a cyclic DMA
> +		 *   transfer to read data from the Receive Holding Register
> +		 *   (RHR) to avoid RX overruns since the RHR is not protected
> +		 *   by any FIFO on most Atmel SoCs. So pausing the DMA transfer
> +		 *   to compute the residue would break the USART driver design.
> +		 * - The atc_pause() function masks interrupts but we'd rather
> +		 *   avoid to do so for system latency purpose.
> +		 *
> +		 * Then we'd rather use another solution: the DSCR is read a
> +		 * first time, the CTRLA is read in turn, next the DSCR is read
> +		 * a second time. If the two consecutive read values of the DSCR
> +		 * are the same then we assume both refers to the very same
> +		 * child descriptor as well as the CTRLA value read inbetween
> +		 * does. For cyclic tranfers, the assumption is that a full loop
> +		 * is "not so fast".
> +		 * If the two DSCR values are different, we read again the CTRLA
> +		 * then the DSCR till two consecutive read values from DSCR are
> +		 * equal or till the maxium trials is reach.
> +		 * This algorithm is very unlikely not to find a stable value for
> +		 * DSCR.
>  		 */
>  
> -		ctrla = channel_readl(atchan, CTRLA);
> -		rmb(); /* ensure CTRLA is read before DSCR */
>  		dscr = channel_readl(atchan, DSCR);
> +		rmb(); /* ensure DSCR is read before CTRLA */
> +		ctrla = channel_readl(atchan, CTRLA);
> +		for (trials = 0; trials < ATC_MAX_DSCR_TRIALS; ++trials) {
> +			u32 new_dscr;
> +
> +			rmb(); /* ensure DSCR is read after CTRLA */
> +			new_dscr = channel_readl(atchan, DSCR);
> +
> +			/*
> +			 * If the DSCR register value has not changed inside the
> +			 * DMA controller since the previous read, we assume
> +			 * that both the dscr and ctrla values refers to the
> +			 * very same descriptor.
> +			 */
> +			if (likely(new_dscr == dscr))
> +				break;
> +
> +			/*
> +			 * DSCR has changed inside the DMA controller, so the
> +			 * previouly read value of CTRLA may refer to an already
> +			 * processed descriptor hence could be outdated.
> +			 * We need to update ctrla to match the current
> +			 * descriptor.
> +			 */
> +			dscr = new_dscr;
> +			rmb(); /* ensure DSCR is read before CTRLA */
> +			ctrla = channel_readl(atchan, CTRLA);
> +		}
> +		if (unlikely(trials >= ATC_MAX_DSCR_TRIALS))
> +			return -ETIMEDOUT;
>  
>  		/* for the first descriptor we can be more accurate */
>  		if (desc_first->lli.dscr == dscr)
> -			return atc_calc_bytes_left(ret, ctrla, desc_first);
> +			return atc_calc_bytes_left(ret, ctrla);
>  
>  		ret -= desc_first->len;
>  		list_for_each_entry(desc, &desc_first->tx_list, desc_node) {
> @@ -365,16 +425,14 @@ static int atc_get_bytes_left(struct dma_chan *chan, dma_cookie_t cookie)
>  		}
>  
>  		/*
> -		 * For the last descriptor in the chain we can calculate
> +		 * For the current descriptor in the chain we can calculate
>  		 * the remaining bytes using the channel's register.
> -		 * Note that the transfer width of the first and last
> -		 * descriptor may differ.
>  		 */
> -		if (!desc->lli.dscr)
> -			ret = atc_calc_bytes_left_from_reg(ret, atchan, desc);
> +		ret = atc_calc_bytes_left(ret, ctrla);
>  	} else {
>  		/* single transfer */
> -		ret = atc_calc_bytes_left_from_reg(ret, atchan, desc_first);
> +		ctrla = channel_readl(atchan, CTRLA);
> +		ret = atc_calc_bytes_left(ret, ctrla);
>  	}
>  
>  	return ret;
> @@ -726,7 +784,6 @@ atc_prep_dma_interleaved(struct dma_chan *chan,
>  
>  	desc->txd.cookie = -EBUSY;
>  	desc->total_len = desc->len = len;
> -	desc->tx_width = dwidth;
>  
>  	/* set end-of-link to the last link descriptor of list*/
>  	set_desc_eol(desc);
> @@ -804,10 +861,6 @@ atc_prep_dma_memcpy(struct dma_chan *chan, dma_addr_t dest, dma_addr_t src,
>  	first->txd.cookie = -EBUSY;
>  	first->total_len = len;
>  
> -	/* set transfer width for the calculation of the residue */
> -	first->tx_width = src_width;
> -	prev->tx_width = src_width;
> -
>  	/* set end-of-link to the last link descriptor of list*/
>  	set_desc_eol(desc);
>  
> @@ -956,10 +1009,6 @@ atc_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl,
>  	first->txd.cookie = -EBUSY;
>  	first->total_len = total_len;
>  
> -	/* set transfer width for the calculation of the residue */
> -	first->tx_width = reg_width;
> -	prev->tx_width = reg_width;
> -
>  	/* first link descriptor of list is responsible of flags */
>  	first->txd.flags = flags; /* client is in control of this ack */
>  
> @@ -1077,12 +1126,6 @@ atc_prep_dma_sg(struct dma_chan *chan,
>  		desc->txd.cookie = 0;
>  		desc->len = len;
>  
> -		/*
> -		 * Although we only need the transfer width for the first and
> -		 * the last descriptor, its easier to set it to all descriptors.
> -		 */
> -		desc->tx_width = src_width;
> -
>  		atc_desc_chain(&first, &prev, desc);
>  
>  		/* update the lengths and addresses for the next loop cycle */
> @@ -1256,7 +1299,6 @@ atc_prep_dma_cyclic(struct dma_chan *chan, dma_addr_t buf_addr, size_t buf_len,
>  	/* First descriptor of the chain embedds additional information */
>  	first->txd.cookie = -EBUSY;
>  	first->total_len = buf_len;
> -	first->tx_width = reg_width;
>  
>  	return &first->txd;
>  
> diff --git a/drivers/dma/at_hdmac_regs.h b/drivers/dma/at_hdmac_regs.h
> index bc8d5eb..7f5a082 100644
> --- a/drivers/dma/at_hdmac_regs.h
> +++ b/drivers/dma/at_hdmac_regs.h
> @@ -112,6 +112,7 @@
>  #define		ATC_SRC_WIDTH_BYTE	(0x0 << 24)
>  #define		ATC_SRC_WIDTH_HALFWORD	(0x1 << 24)
>  #define		ATC_SRC_WIDTH_WORD	(0x2 << 24)
> +#define		ATC_REG_TO_SRC_WIDTH(r)	(((r) >> 24) & 0x3)
>  #define	ATC_DST_WIDTH_MASK	(0x3 << 28)	/* Destination Single Transfer Size */
>  #define		ATC_DST_WIDTH(x)	((x) << 28)
>  #define		ATC_DST_WIDTH_BYTE	(0x0 << 28)
> @@ -182,7 +183,6 @@ struct at_lli {
>   * @txd: support for the async_tx api
>   * @desc_node: node on the channed descriptors list
>   * @len: descriptor byte count
> - * @tx_width: transfer width
>   * @total_len: total transaction byte count
>   */
>  struct at_desc {
> @@ -194,7 +194,6 @@ struct at_desc {
>  	struct dma_async_tx_descriptor	txd;
>  	struct list_head		desc_node;
>  	size_t				len;
> -	u32				tx_width;
>  	size_t				total_len;
>  
>  	/* Interleaved data */
> 


-- 
Nicolas Ferre



More information about the linux-arm-kernel mailing list