[PATCH 01/10] wcn36xx: fix buffer commit logic on TX path
Ramon Fried
ramon.fried at gmail.com
Thu May 17 02:03:54 PDT 2018
On Wed, May 16, 2018 at 5:08 PM, Daniel Mack <daniel at zonque.org> wrote:
> When wcn36xx_dxe_tx_frame() is entered while the device is still processing
> the queue asyncronously, we are racing against the firmware code with
> updates to the buffer descriptors. Presumably, the firmware scans the ring
> buffer that holds the descriptors and scans for a valid control descriptor,
> and then assumes that the next descriptor contains the payload. If, however,
> the control descriptor is marked valid, but the payload descriptor isn't,
> the packet is not sent out.
>
> Another issue with the current code is that is lacks memory barriers before
> descriptors are marked valid. This is important because the CPU may reorder
> writes to memory, even if it is allocated as coherent DMA area, and hence
> the device may see incompletely written data.
>
> To fix this, the code in wcn36xx_dxe_tx_frame() was restructured a bit so
> that the payload descriptor is made valid before the control descriptor.
> Memory barriers are added to ensure coherency of shared memory areas.
>
> Signed-off-by: Daniel Mack <daniel at zonque.org>
> ---
> drivers/net/wireless/ath/wcn36xx/dxe.c | 75 +++++++++++++++++-----------------
> 1 file changed, 38 insertions(+), 37 deletions(-)
>
> diff --git a/drivers/net/wireless/ath/wcn36xx/dxe.c b/drivers/net/wireless/ath/wcn36xx/dxe.c
> index bd2b946a65c9..3d1cf7dd1f8e 100644
> --- a/drivers/net/wireless/ath/wcn36xx/dxe.c
> +++ b/drivers/net/wireless/ath/wcn36xx/dxe.c
> @@ -642,8 +642,8 @@ int wcn36xx_dxe_tx_frame(struct wcn36xx *wcn,
> struct sk_buff *skb,
> bool is_low)
> {
> - struct wcn36xx_dxe_ctl *ctl = NULL;
> - struct wcn36xx_dxe_desc *desc = NULL;
> + struct wcn36xx_dxe_desc *desc_bd, *desc_skb;
> + struct wcn36xx_dxe_ctl *ctl_bd, *ctl_skb;
> struct wcn36xx_dxe_ch *ch = NULL;
> unsigned long flags;
> int ret;
> @@ -651,74 +651,75 @@ int wcn36xx_dxe_tx_frame(struct wcn36xx *wcn,
> ch = is_low ? &wcn->dxe_tx_l_ch : &wcn->dxe_tx_h_ch;
>
> spin_lock_irqsave(&ch->lock, flags);
> - ctl = ch->head_blk_ctl;
> + ctl_bd = ch->head_blk_ctl;
> + ctl_skb = ctl_bd->next;
>
> /*
> * If skb is not null that means that we reached the tail of the ring
> * hence ring is full. Stop queues to let mac80211 back off until ring
> * has an empty slot again.
> */
> - if (NULL != ctl->next->skb) {
> + if (NULL != ctl_skb->skb) {
> ieee80211_stop_queues(wcn->hw);
> wcn->queues_stopped = true;
> spin_unlock_irqrestore(&ch->lock, flags);
> return -EBUSY;
> }
>
> - ctl->skb = NULL;
> - desc = ctl->desc;
> + if (unlikely(ctl_skb->bd_cpu_addr)) {
> + wcn36xx_err("bd_cpu_addr cannot be NULL for skb DXE\n");
> + ret = -EINVAL;
> + goto unlock;
> + }
> +
> + desc_bd = ctl_bd->desc;
> + desc_skb = ctl_skb->desc;
> +
> + ctl_bd->skb = NULL;
>
> /* write buffer descriptor */
> - memcpy(ctl->bd_cpu_addr, bd, sizeof(*bd));
> + memcpy(ctl_bd->bd_cpu_addr, bd, sizeof(*bd));
>
> /* Set source address of the BD we send */
> - desc->src_addr_l = ctl->bd_phy_addr;
> -
> - desc->dst_addr_l = ch->dxe_wq;
> - desc->fr_len = sizeof(struct wcn36xx_tx_bd);
> - desc->ctrl = ch->ctrl_bd;
> + desc_bd->src_addr_l = ctl_bd->bd_phy_addr;
> + desc_bd->dst_addr_l = ch->dxe_wq;
> + desc_bd->fr_len = sizeof(struct wcn36xx_tx_bd);
>
> wcn36xx_dbg(WCN36XX_DBG_DXE, "DXE TX\n");
>
> wcn36xx_dbg_dump(WCN36XX_DBG_DXE_DUMP, "DESC1 >>> ",
> - (char *)desc, sizeof(*desc));
> + (char *)desc_bd, sizeof(*desc_bd));
> wcn36xx_dbg_dump(WCN36XX_DBG_DXE_DUMP,
> - "BD >>> ", (char *)ctl->bd_cpu_addr,
> + "BD >>> ", (char *)ctl_bd->bd_cpu_addr,
> sizeof(struct wcn36xx_tx_bd));
>
> - /* Set source address of the SKB we send */
> - ctl = ctl->next;
> - desc = ctl->desc;
> - if (ctl->bd_cpu_addr) {
> - wcn36xx_err("bd_cpu_addr cannot be NULL for skb DXE\n");
> - ret = -EINVAL;
> - goto unlock;
> - }
> -
> - desc->src_addr_l = dma_map_single(wcn->dev,
> - skb->data,
> - skb->len,
> - DMA_TO_DEVICE);
> - if (dma_mapping_error(wcn->dev, desc->src_addr_l)) {
> + desc_skb->src_addr_l = dma_map_single(wcn->dev,
> + skb->data,
> + skb->len,
> + DMA_TO_DEVICE);
> + if (dma_mapping_error(wcn->dev, desc_skb->src_addr_l)) {
> dev_err(wcn->dev, "unable to DMA map src_addr_l\n");
> ret = -ENOMEM;
> goto unlock;
> }
>
> - ctl->skb = skb;
> - desc->dst_addr_l = ch->dxe_wq;
> - desc->fr_len = ctl->skb->len;
> -
> - /* set dxe descriptor to VALID */
> - desc->ctrl = ch->ctrl_skb;
> + ctl_skb->skb = skb;
> + desc_skb->dst_addr_l = ch->dxe_wq;
> + desc_skb->fr_len = ctl_skb->skb->len;
>
> wcn36xx_dbg_dump(WCN36XX_DBG_DXE_DUMP, "DESC2 >>> ",
> - (char *)desc, sizeof(*desc));
> + (char *)desc_skb, sizeof(*desc_skb));
> wcn36xx_dbg_dump(WCN36XX_DBG_DXE_DUMP, "SKB >>> ",
> - (char *)ctl->skb->data, ctl->skb->len);
> + (char *)ctl_skb->skb->data, ctl_skb->skb->len);
>
> /* Move the head of the ring to the next empty descriptor */
> - ch->head_blk_ctl = ctl->next;
> + ch->head_blk_ctl = ctl_skb->next;
> +
> + /* Commit all previous writes and set descriptors to VALID */
> + wmb();
> + desc_skb->ctrl = ch->ctrl_skb;
> + wmb();
> + desc_bd->ctrl = ch->ctrl_bd;
>
> /*
> * When connected and trying to send data frame chip can be in sleep
> --
> 2.14.3
>
>
> _______________________________________________
> wcn36xx mailing list
> wcn36xx at lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/wcn36xx
Acked-by: Ramon Fried <ramon.fried at linaro.org>
More information about the wcn36xx
mailing list