Commit a9e79b11 authored by Loic Poulain's avatar Loic Poulain Committed by Kalle Valo

wcn36xx: Fix tx_status mechanism

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: fdf21cc3 ("wcn36xx: Add TX ack support")
Signed-off-by: default avatarLoic Poulain <loic.poulain@linaro.org>
Signed-off-by: default avatarKalle Valo <kvalo@codeaurora.org>
Link: https://lore.kernel.org/r/1634567281-28997-1-git-send-email-loic.poulain@linaro.org
parent d3fd2c95
...@@ -403,8 +403,21 @@ static void reap_tx_dxes(struct wcn36xx *wcn, struct wcn36xx_dxe_ch *ch) ...@@ -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, dma_unmap_single(wcn->dev, ctl->desc->src_addr_l,
ctl->skb->len, DMA_TO_DEVICE); ctl->skb->len, DMA_TO_DEVICE);
info = IEEE80211_SKB_CB(ctl->skb); info = IEEE80211_SKB_CB(ctl->skb);
if (!(info->flags & IEEE80211_TX_CTL_REQ_TX_STATUS)) { if (info->flags & IEEE80211_TX_CTL_REQ_TX_STATUS) {
/* Keep frame until TX status comes */ 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); ieee80211_free_txskb(wcn->hw, ctl->skb);
} }
...@@ -426,7 +439,6 @@ static irqreturn_t wcn36xx_irq_tx_complete(int irq, void *dev) ...@@ -426,7 +439,6 @@ static irqreturn_t wcn36xx_irq_tx_complete(int irq, void *dev)
{ {
struct wcn36xx *wcn = (struct wcn36xx *)dev; struct wcn36xx *wcn = (struct wcn36xx *)dev;
int int_src, int_reason; int int_src, int_reason;
bool transmitted = false;
wcn36xx_dxe_read_register(wcn, WCN36XX_DXE_INT_SRC_RAW_REG, &int_src); wcn36xx_dxe_read_register(wcn, WCN36XX_DXE_INT_SRC_RAW_REG, &int_src);
...@@ -466,7 +478,6 @@ static irqreturn_t wcn36xx_irq_tx_complete(int irq, void *dev) ...@@ -466,7 +478,6 @@ static irqreturn_t wcn36xx_irq_tx_complete(int irq, void *dev)
if (int_reason & (WCN36XX_CH_STAT_INT_DONE_MASK | if (int_reason & (WCN36XX_CH_STAT_INT_DONE_MASK |
WCN36XX_CH_STAT_INT_ED_MASK)) { WCN36XX_CH_STAT_INT_ED_MASK)) {
reap_tx_dxes(wcn, &wcn->dxe_tx_h_ch); reap_tx_dxes(wcn, &wcn->dxe_tx_h_ch);
transmitted = true;
} }
} }
...@@ -479,7 +490,6 @@ static irqreturn_t wcn36xx_irq_tx_complete(int irq, void *dev) ...@@ -479,7 +490,6 @@ static irqreturn_t wcn36xx_irq_tx_complete(int irq, void *dev)
WCN36XX_DXE_0_INT_CLR, WCN36XX_DXE_0_INT_CLR,
WCN36XX_INT_MASK_CHAN_TX_L); WCN36XX_INT_MASK_CHAN_TX_L);
if (int_reason & WCN36XX_CH_STAT_INT_ERR_MASK ) { if (int_reason & WCN36XX_CH_STAT_INT_ERR_MASK ) {
wcn36xx_dxe_write_register(wcn, wcn36xx_dxe_write_register(wcn,
WCN36XX_DXE_0_INT_ERR_CLR, WCN36XX_DXE_0_INT_ERR_CLR,
...@@ -507,25 +517,8 @@ static irqreturn_t wcn36xx_irq_tx_complete(int irq, void *dev) ...@@ -507,25 +517,8 @@ static irqreturn_t wcn36xx_irq_tx_complete(int irq, void *dev)
if (int_reason & (WCN36XX_CH_STAT_INT_DONE_MASK | if (int_reason & (WCN36XX_CH_STAT_INT_DONE_MASK |
WCN36XX_CH_STAT_INT_ED_MASK)) { WCN36XX_CH_STAT_INT_ED_MASK)) {
reap_tx_dxes(wcn, &wcn->dxe_tx_l_ch); 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; return IRQ_HANDLED;
} }
......
...@@ -503,10 +503,11 @@ int wcn36xx_start_tx(struct wcn36xx *wcn, ...@@ -503,10 +503,11 @@ int wcn36xx_start_tx(struct wcn36xx *wcn,
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
struct wcn36xx_vif *vif_priv = NULL; struct wcn36xx_vif *vif_priv = NULL;
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
unsigned long flags;
bool is_low = ieee80211_is_data(hdr->frame_control); bool is_low = ieee80211_is_data(hdr->frame_control);
bool bcast = is_broadcast_ether_addr(hdr->addr1) || bool bcast = is_broadcast_ether_addr(hdr->addr1) ||
is_multicast_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; struct wcn36xx_tx_bd bd;
int ret; int ret;
...@@ -522,29 +523,15 @@ int wcn36xx_start_tx(struct wcn36xx *wcn, ...@@ -522,29 +523,15 @@ int wcn36xx_start_tx(struct wcn36xx *wcn,
bd.dpu_rf = WCN36XX_BMU_WQ_TX; 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"); 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 /* Only one at a time is supported by fw. Stop the TX queues
* until the ack status gets back. * until the ack status gets back.
*/ */
ieee80211_stop_queues(wcn->hw); 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 */ /* Request ack indication from the firmware */
if (!(info->flags & IEEE80211_TX_CTL_NO_ACK))
bd.tx_comp = 1; bd.tx_comp = 1;
} }
...@@ -559,14 +546,8 @@ int wcn36xx_start_tx(struct wcn36xx *wcn, ...@@ -559,14 +546,8 @@ int wcn36xx_start_tx(struct wcn36xx *wcn,
bd.tx_bd_sign = 0xbdbdbdbd; bd.tx_bd_sign = 0xbdbdbdbd;
ret = wcn36xx_dxe_tx_frame(wcn, vif_priv, &bd, skb, is_low); ret = wcn36xx_dxe_tx_frame(wcn, vif_priv, &bd, skb, is_low);
if (ret && (info->flags & IEEE80211_TX_CTL_REQ_TX_STATUS)) { if (unlikely(ret && ack_ind)) {
/* If the skb has not been transmitted, /* If the skb has not been transmitted, resume TX queue */
* 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);
ieee80211_wake_queues(wcn->hw); ieee80211_wake_queues(wcn->hw);
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment