[PATCH 5.10 104/575] wcn36xx: Fix tx_status mechanism

From: Greg Kroah-Hartman
Date: Mon Nov 15 2021 - 14:10:31 EST


From: Loic Poulain <loic.poulain@xxxxxxxxxx>

commit a9e79b116cc4d0057e912be8f40b2c2e5bdc7c43 upstream.

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@xxxxxxxxxxxxxxx
Fixes: fdf21cc37149 ("wcn36xx: Add TX ack support")
Signed-off-by: Loic Poulain <loic.poulain@xxxxxxxxxx>
Signed-off-by: Kalle Valo <kvalo@xxxxxxxxxxxxxx>
Link: https://lore.kernel.org/r/1634567281-28997-1-git-send-email-loic.poulain@xxxxxxxxxx
Signed-off-by: Greg Kroah-Hartman <gregkh@xxxxxxxxxxxxxxxxxxx>
---
drivers/net/wireless/ath/wcn36xx/dxe.c | 37 ++++++++++++--------------------
drivers/net/wireless/ath/wcn36xx/txrx.c | 31 +++++---------------------
2 files changed, 21 insertions(+), 47 deletions(-)

--- a/drivers/net/wireless/ath/wcn36xx/dxe.c
+++ b/drivers/net/wireless/ath/wcn36xx/dxe.c
@@ -403,8 +403,21 @@ static void reap_tx_dxes(struct wcn36xx
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);
}

@@ -426,7 +439,6 @@ static irqreturn_t wcn36xx_irq_tx_comple
{
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);

@@ -466,7 +478,6 @@ static irqreturn_t wcn36xx_irq_tx_comple
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;
}
}

@@ -479,7 +490,6 @@ static irqreturn_t wcn36xx_irq_tx_comple
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,
@@ -507,25 +517,8 @@ static irqreturn_t wcn36xx_irq_tx_comple
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;
}
--- a/drivers/net/wireless/ath/wcn36xx/txrx.c
+++ b/drivers/net/wireless/ath/wcn36xx/txrx.c
@@ -502,10 +502,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;

@@ -521,30 +522,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*/
@@ -558,14 +545,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);
}