Skip to content

Commit

Permalink
wcn36xx: Fix tx_status mechanism
Browse files Browse the repository at this point in the history
This change fix the TX ack mechanism in various ways:

- For NO_ACK tagged packets, we don't need to wait for TX_ACK indication
and so are not subject to the single packet ack limitation. So we don't
have to stop the tx queue, and can call the tx status callback as soon
as DMA transfer has completed.

- Fix skb ownership/reference. Only start status indication timeout
once the DMA transfer has been completed. This avoids the skb to be
both referenced in the DMA tx ring and by the tx_ack_skb pointer,
preventing any use-after-free or double-free.

- This adds a sanity (paranoia?) check on the skb tx ack pointer.

- Resume TX queue if TX status tagged packet TX fails.

Cc: stable@vger.kernel.org
Fixes: fdf21cc ("wcn36xx: Add TX ack support")
Signed-off-by: Loic Poulain <loic.poulain@linaro.org>
Signed-off-by: Kalle Valo <kvalo@codeaurora.org>
Link: https://lore.kernel.org/r/1634567281-28997-1-git-send-email-loic.poulain@linaro.org
  • Loading branch information
Loic Poulain authored and Kalle Valo committed Oct 25, 2021
1 parent d3fd2c9 commit a9e79b1
Show file tree
Hide file tree
Showing 2 changed files with 21 additions and 47 deletions.
37 changes: 15 additions & 22 deletions drivers/net/wireless/ath/wcn36xx/dxe.c
Original file line number Diff line number Diff line change
Expand Up @@ -403,8 +403,21 @@ static void reap_tx_dxes(struct wcn36xx *wcn, struct wcn36xx_dxe_ch *ch)
dma_unmap_single(wcn->dev, ctl->desc->src_addr_l,
ctl->skb->len, DMA_TO_DEVICE);
info = IEEE80211_SKB_CB(ctl->skb);
if (!(info->flags & IEEE80211_TX_CTL_REQ_TX_STATUS)) {
/* Keep frame until TX status comes */
if (info->flags & IEEE80211_TX_CTL_REQ_TX_STATUS) {
if (info->flags & IEEE80211_TX_CTL_NO_ACK) {
info->flags |= IEEE80211_TX_STAT_NOACK_TRANSMITTED;
ieee80211_tx_status_irqsafe(wcn->hw, ctl->skb);
} else {
/* Wait for the TX ack indication or timeout... */
spin_lock(&wcn->dxe_lock);
if (WARN_ON(wcn->tx_ack_skb))
ieee80211_free_txskb(wcn->hw, wcn->tx_ack_skb);
wcn->tx_ack_skb = ctl->skb; /* Tracking ref */
mod_timer(&wcn->tx_ack_timer, jiffies + HZ / 10);
spin_unlock(&wcn->dxe_lock);
}
/* do not free, ownership transferred to mac80211 status cb */
} else {
ieee80211_free_txskb(wcn->hw, ctl->skb);
}

Expand All @@ -426,7 +439,6 @@ static irqreturn_t wcn36xx_irq_tx_complete(int irq, void *dev)
{
struct wcn36xx *wcn = (struct wcn36xx *)dev;
int int_src, int_reason;
bool transmitted = false;

wcn36xx_dxe_read_register(wcn, WCN36XX_DXE_INT_SRC_RAW_REG, &int_src);

Expand Down Expand Up @@ -466,7 +478,6 @@ static irqreturn_t wcn36xx_irq_tx_complete(int irq, void *dev)
if (int_reason & (WCN36XX_CH_STAT_INT_DONE_MASK |
WCN36XX_CH_STAT_INT_ED_MASK)) {
reap_tx_dxes(wcn, &wcn->dxe_tx_h_ch);
transmitted = true;
}
}

Expand All @@ -479,7 +490,6 @@ static irqreturn_t wcn36xx_irq_tx_complete(int irq, void *dev)
WCN36XX_DXE_0_INT_CLR,
WCN36XX_INT_MASK_CHAN_TX_L);


if (int_reason & WCN36XX_CH_STAT_INT_ERR_MASK ) {
wcn36xx_dxe_write_register(wcn,
WCN36XX_DXE_0_INT_ERR_CLR,
Expand Down Expand Up @@ -507,25 +517,8 @@ static irqreturn_t wcn36xx_irq_tx_complete(int irq, void *dev)
if (int_reason & (WCN36XX_CH_STAT_INT_DONE_MASK |
WCN36XX_CH_STAT_INT_ED_MASK)) {
reap_tx_dxes(wcn, &wcn->dxe_tx_l_ch);
transmitted = true;
}
}

spin_lock(&wcn->dxe_lock);
if (wcn->tx_ack_skb && transmitted) {
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(wcn->tx_ack_skb);

/* TX complete, no need to wait for 802.11 ack indication */
if (info->flags & IEEE80211_TX_CTL_REQ_TX_STATUS &&
info->flags & IEEE80211_TX_CTL_NO_ACK) {
info->flags |= IEEE80211_TX_STAT_NOACK_TRANSMITTED;
del_timer(&wcn->tx_ack_timer);
ieee80211_tx_status_irqsafe(wcn->hw, wcn->tx_ack_skb);
wcn->tx_ack_skb = NULL;
ieee80211_wake_queues(wcn->hw);
}
}
spin_unlock(&wcn->dxe_lock);

return IRQ_HANDLED;
}
Expand Down
31 changes: 6 additions & 25 deletions drivers/net/wireless/ath/wcn36xx/txrx.c
Original file line number Diff line number Diff line change
Expand Up @@ -503,10 +503,11 @@ int wcn36xx_start_tx(struct wcn36xx *wcn,
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
struct wcn36xx_vif *vif_priv = NULL;
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
unsigned long flags;
bool is_low = ieee80211_is_data(hdr->frame_control);
bool bcast = is_broadcast_ether_addr(hdr->addr1) ||
is_multicast_ether_addr(hdr->addr1);
bool ack_ind = (info->flags & IEEE80211_TX_CTL_REQ_TX_STATUS) &&
!(info->flags & IEEE80211_TX_CTL_NO_ACK);
struct wcn36xx_tx_bd bd;
int ret;

Expand All @@ -522,30 +523,16 @@ int wcn36xx_start_tx(struct wcn36xx *wcn,

bd.dpu_rf = WCN36XX_BMU_WQ_TX;

if (info->flags & IEEE80211_TX_CTL_REQ_TX_STATUS) {
if (unlikely(ack_ind)) {
wcn36xx_dbg(WCN36XX_DBG_DXE, "TX_ACK status requested\n");

spin_lock_irqsave(&wcn->dxe_lock, flags);
if (wcn->tx_ack_skb) {
spin_unlock_irqrestore(&wcn->dxe_lock, flags);
wcn36xx_warn("tx_ack_skb already set\n");
return -EINVAL;
}

wcn->tx_ack_skb = skb;
spin_unlock_irqrestore(&wcn->dxe_lock, flags);

/* Only one at a time is supported by fw. Stop the TX queues
* until the ack status gets back.
*/
ieee80211_stop_queues(wcn->hw);

/* TX watchdog if no TX irq or ack indication received */
mod_timer(&wcn->tx_ack_timer, jiffies + HZ / 10);

/* Request ack indication from the firmware */
if (!(info->flags & IEEE80211_TX_CTL_NO_ACK))
bd.tx_comp = 1;
bd.tx_comp = 1;
}

/* Data frames served first*/
Expand All @@ -559,14 +546,8 @@ int wcn36xx_start_tx(struct wcn36xx *wcn,
bd.tx_bd_sign = 0xbdbdbdbd;

ret = wcn36xx_dxe_tx_frame(wcn, vif_priv, &bd, skb, is_low);
if (ret && (info->flags & IEEE80211_TX_CTL_REQ_TX_STATUS)) {
/* If the skb has not been transmitted,
* don't keep a reference to it.
*/
spin_lock_irqsave(&wcn->dxe_lock, flags);
wcn->tx_ack_skb = NULL;
spin_unlock_irqrestore(&wcn->dxe_lock, flags);

if (unlikely(ret && ack_ind)) {
/* If the skb has not been transmitted, resume TX queue */
ieee80211_wake_queues(wcn->hw);
}

Expand Down

0 comments on commit a9e79b1

Please sign in to comment.