b03622d
From patchwork Thu Mar 15 11:31:33 2018
b03622d
Content-Type: text/plain; charset="utf-8"
b03622d
MIME-Version: 1.0
b03622d
Content-Transfer-Encoding: 7bit
b03622d
Subject: wcn36xx: Fix firmware crash due to corrupted buffer address
b03622d
X-Patchwork-Submitter: Ramon Fried <rfried@codeaurora.org>
b03622d
X-Patchwork-Id: 131764
b03622d
Message-Id: <20180315113133.28791-1-rfried@codeaurora.org>
b03622d
To: k.eugene.e@gmail.com, kvalo@codeaurora.org,
b03622d
 wcn36xx@lists.infradead.org, linux-wireless@vger.kernel.org
b03622d
Cc: Loic Poulain <loic.poulain@linaro.org>, Ramon Fried <rfried@codeaurora.org>
b03622d
Date: Thu, 15 Mar 2018 13:31:33 +0200
b03622d
From: Ramon Fried <rfried@codeaurora.org>
b03622d
List-Id: <linux-wireless.vger.kernel.org>
b03622d
b03622d
From: Loic Poulain <loic.poulain@linaro.org>
b03622d

b03622d
wcn36xx_start_tx function retrieves the buffer descriptor from the
b03622d
channel control queue to start filling tx buffer information. However,
b03622d
nothing prevents this same buffer to be concurrently accessed in a
b03622d
concurent tx call, leading to potential buffer coruption and firmware
b03622d
crash (observed during iperf test). The channel control queue should
b03622d
only be accessed and updated with the channel lock.
b03622d

b03622d
Fix this issue by using a local buffer descriptor which will be copied
b03622d
in the thread-safe wcn36xx_dxe_tx_frame.
b03622d

b03622d
Note that buffer descriptor size is few bytes so the introduced copy
b03622d
overhead is insignificant. Moreover, this allows to keep the locked
b03622d
section minimal.
b03622d

b03622d
Signed-off-by: Loic Poulain <loic.poulain@linaro.org>
b03622d
Signed-off-by: Ramon Fried <rfried@codeaurora.org>
b03622d
---
b03622d
 drivers/net/wireless/ath/wcn36xx/dxe.c  | 13 ++++---------
b03622d
 drivers/net/wireless/ath/wcn36xx/dxe.h  |  3 ++-
b03622d
 drivers/net/wireless/ath/wcn36xx/txrx.c | 32 ++++++++++----------------------
b03622d
 3 files changed, 16 insertions(+), 32 deletions(-)
b03622d

b03622d
-- 
b03622d
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
b03622d
a Linux Foundation Collaborative Project
b03622d
b03622d
diff --git a/drivers/net/wireless/ath/wcn36xx/dxe.c b/drivers/net/wireless/ath/wcn36xx/dxe.c
b03622d
index 7d5ecaf02288..2c3b899a88fa 100644
b03622d
--- a/drivers/net/wireless/ath/wcn36xx/dxe.c
b03622d
+++ b/drivers/net/wireless/ath/wcn36xx/dxe.c
b03622d
@@ -27,15 +27,6 @@
b03622d
 #include "wcn36xx.h"
b03622d
 #include "txrx.h"
b03622d
 
b03622d
-void *wcn36xx_dxe_get_next_bd(struct wcn36xx *wcn, bool is_low)
b03622d
-{
b03622d
-	struct wcn36xx_dxe_ch *ch = is_low ?
b03622d
-		&wcn->dxe_tx_l_ch :
b03622d
-		&wcn->dxe_tx_h_ch;
b03622d
-
b03622d
-	return ch->head_blk_ctl->bd_cpu_addr;
b03622d
-}
b03622d
-
b03622d
 static void wcn36xx_ccu_write_register(struct wcn36xx *wcn, int addr, int data)
b03622d
 {
b03622d
 	wcn36xx_dbg(WCN36XX_DBG_DXE,
b03622d
@@ -648,6 +639,7 @@ void wcn36xx_dxe_free_mem_pools(struct wcn36xx *wcn)
b03622d
 
b03622d
 int wcn36xx_dxe_tx_frame(struct wcn36xx *wcn,
b03622d
 			 struct wcn36xx_vif *vif_priv,
b03622d
+			 struct wcn36xx_tx_bd *bd,
b03622d
 			 struct sk_buff *skb,
b03622d
 			 bool is_low)
b03622d
 {
b03622d
@@ -681,6 +673,9 @@ int wcn36xx_dxe_tx_frame(struct wcn36xx *wcn,
b03622d
 	ctl->skb = NULL;
b03622d
 	desc = ctl->desc;
b03622d
 
b03622d
+	/* write buffer descriptor */
b03622d
+	memcpy(ctl->bd_cpu_addr, bd, sizeof(*bd));
b03622d
+
b03622d
 	/* Set source address of the BD we send */
b03622d
 	desc->src_addr_l = ctl->bd_phy_addr;
b03622d
 
b03622d
diff --git a/drivers/net/wireless/ath/wcn36xx/dxe.h b/drivers/net/wireless/ath/wcn36xx/dxe.h
b03622d
index 2bc376c5391b..ce580960d109 100644
b03622d
--- a/drivers/net/wireless/ath/wcn36xx/dxe.h
b03622d
+++ b/drivers/net/wireless/ath/wcn36xx/dxe.h
b03622d
@@ -452,6 +452,7 @@ struct wcn36xx_dxe_mem_pool {
b03622d
 	dma_addr_t	phy_addr;
b03622d
 };
b03622d
 
b03622d
+struct wcn36xx_tx_bd;
b03622d
 struct wcn36xx_vif;
b03622d
 int wcn36xx_dxe_allocate_mem_pools(struct wcn36xx *wcn);
b03622d
 void wcn36xx_dxe_free_mem_pools(struct wcn36xx *wcn);
b03622d
@@ -463,8 +464,8 @@ void wcn36xx_dxe_deinit(struct wcn36xx *wcn);
b03622d
 int wcn36xx_dxe_init_channels(struct wcn36xx *wcn);
b03622d
 int wcn36xx_dxe_tx_frame(struct wcn36xx *wcn,
b03622d
 			 struct wcn36xx_vif *vif_priv,
b03622d
+			 struct wcn36xx_tx_bd *bd,
b03622d
 			 struct sk_buff *skb,
b03622d
 			 bool is_low);
b03622d
 void wcn36xx_dxe_tx_ack_ind(struct wcn36xx *wcn, u32 status);
b03622d
-void *wcn36xx_dxe_get_next_bd(struct wcn36xx *wcn, bool is_low);
b03622d
 #endif	/* _DXE_H_ */
b03622d
diff --git a/drivers/net/wireless/ath/wcn36xx/txrx.c b/drivers/net/wireless/ath/wcn36xx/txrx.c
b03622d
index 22304edc5948..b1768ed6b0be 100644
b03622d
--- a/drivers/net/wireless/ath/wcn36xx/txrx.c
b03622d
+++ b/drivers/net/wireless/ath/wcn36xx/txrx.c
b03622d
@@ -272,21 +272,9 @@ int wcn36xx_start_tx(struct wcn36xx *wcn,
b03622d
 	bool is_low = ieee80211_is_data(hdr->frame_control);
b03622d
 	bool bcast = is_broadcast_ether_addr(hdr->addr1) ||
b03622d
 		is_multicast_ether_addr(hdr->addr1);
b03622d
-	struct wcn36xx_tx_bd *bd = wcn36xx_dxe_get_next_bd(wcn, is_low);
b03622d
-
b03622d
-	if (!bd) {
b03622d
-		/*
b03622d
-		 * TX DXE are used in pairs. One for the BD and one for the
b03622d
-		 * actual frame. The BD DXE's has a preallocated buffer while
b03622d
-		 * the skb ones does not. If this isn't true something is really
b03622d
-		 * wierd. TODO: Recover from this situation
b03622d
-		 */
b03622d
-
b03622d
-		wcn36xx_err("bd address may not be NULL for BD DXE\n");
b03622d
-		return -EINVAL;
b03622d
-	}
b03622d
+	struct wcn36xx_tx_bd bd;
b03622d
 
b03622d
-	memset(bd, 0, sizeof(*bd));
b03622d
+	memset(&bd, 0, sizeof(bd));
b03622d
 
b03622d
 	wcn36xx_dbg(WCN36XX_DBG_TX,
b03622d
 		    "tx skb %p len %d fc %04x sn %d %s %s\n",
b03622d
@@ -296,10 +284,10 @@ int wcn36xx_start_tx(struct wcn36xx *wcn,
b03622d
 
b03622d
 	wcn36xx_dbg_dump(WCN36XX_DBG_TX_DUMP, "", skb->data, skb->len);
b03622d
 
b03622d
-	bd->dpu_rf = WCN36XX_BMU_WQ_TX;
b03622d
+	bd.dpu_rf = WCN36XX_BMU_WQ_TX;
b03622d
 
b03622d
-	bd->tx_comp = !!(info->flags & IEEE80211_TX_CTL_REQ_TX_STATUS);
b03622d
-	if (bd->tx_comp) {
b03622d
+	bd.tx_comp = !!(info->flags & IEEE80211_TX_CTL_REQ_TX_STATUS);
b03622d
+	if (bd.tx_comp) {
b03622d
 		wcn36xx_dbg(WCN36XX_DBG_DXE, "TX_ACK status requested\n");
b03622d
 		spin_lock_irqsave(&wcn->dxe_lock, flags);
b03622d
 		if (wcn->tx_ack_skb) {
b03622d
@@ -321,13 +309,13 @@ int wcn36xx_start_tx(struct wcn36xx *wcn,
b03622d
 
b03622d
 	/* Data frames served first*/
b03622d
 	if (is_low)
b03622d
-		wcn36xx_set_tx_data(bd, wcn, &vif_priv, sta_priv, skb, bcast);
b03622d
+		wcn36xx_set_tx_data(&bd, wcn, &vif_priv, sta_priv, skb, bcast);
b03622d
 	else
b03622d
 		/* MGMT and CTRL frames are handeld here*/
b03622d
-		wcn36xx_set_tx_mgmt(bd, wcn, &vif_priv, skb, bcast);
b03622d
+		wcn36xx_set_tx_mgmt(&bd, wcn, &vif_priv, skb, bcast);
b03622d
 
b03622d
-	buff_to_be((u32 *)bd, sizeof(*bd)/sizeof(u32));
b03622d
-	bd->tx_bd_sign = 0xbdbdbdbd;
b03622d
+	buff_to_be((u32 *)&bd, sizeof(bd)/sizeof(u32));
b03622d
+	bd.tx_bd_sign = 0xbdbdbdbd;
b03622d
 
b03622d
-	return wcn36xx_dxe_tx_frame(wcn, vif_priv, skb, is_low);
b03622d
+	return wcn36xx_dxe_tx_frame(wcn, vif_priv, &bd, skb, is_low);
b03622d
 }