Browse Source

qcacld-3.0: Fix peer unmap/map handling during roaming

Roam_synch indication processing in host performs peer detach/attach
operations after firmware has already moved to the new peer. Out of
sequence peer unmap and map events can mess up the reference count
for reused peer_id values, which can lead to crash.
Solution:
While detaching a peer during roam sync indication processing,
copy its peer_id_ref_cnt in peer_id_to_obj array to new variable
in the same peer map for that peer id. Peer is deleted at that point.
When the unmap events come in, decrement the old ref_cnt and
map events increment the real ref_cnt.
Once the old ref_cnt goes to 0, subsequent unmap operations apply to
the real peer.

CRs-Fixed: 1063177
Change-Id: I9b20f28f17dea1647a213b9f36060109264addf0
Deepak Dhamdhere 8 năm trước cách đây
mục cha
commit
f74d6f8a97

+ 47 - 15
core/dp/txrx/ol_txrx.c

@@ -1224,12 +1224,6 @@ ol_txrx_pdev_post_attach(ol_txrx_pdev_handle pdev)
 			c_element->tx_desc.htt_frag_desc = htt_frag_desc;
 			c_element->tx_desc.htt_frag_desc_paddr = frag_paddr;
 		}
-		QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_INFO_LOW,
-			"%s:%d - %d FRAG VA 0x%p FRAG PA 0x%llx",
-			__func__, __LINE__, i,
-			c_element->tx_desc.htt_frag_desc,
-			(long long unsigned int)
-			c_element->tx_desc.htt_frag_desc_paddr);
 #ifdef QCA_SUPPORT_TXDESC_SANITY_CHECKS
 		c_element->tx_desc.pkt_type = 0xff;
 #ifdef QCA_COMPUTE_TX_DELAY
@@ -2790,15 +2784,15 @@ void ol_txrx_peer_unref_delete(ol_txrx_peer_handle peer)
 	 * concurrently with the empty check.
 	 */
 	qdf_spin_lock_bh(&pdev->peer_ref_mutex);
+
 	if (qdf_atomic_dec_and_test(&peer->ref_cnt)) {
 		u_int16_t peer_id;
 
 		TXRX_PRINT(TXRX_PRINT_LEVEL_ERR,
-			   "Deleting peer %p (%02x:%02x:%02x:%02x:%02x:%02x)",
+			   "Deleting peer %p (%pM) ref_cnt %d\n",
 			   peer,
-			   peer->mac_addr.raw[0], peer->mac_addr.raw[1],
-			   peer->mac_addr.raw[2], peer->mac_addr.raw[3],
-			   peer->mac_addr.raw[4], peer->mac_addr.raw[5]);
+			   peer->mac_addr.raw,
+			   qdf_atomic_read(&peer->ref_cnt));
 
 		peer_id = peer->local_id;
 		/* remove the reference to the peer from the hash table */
@@ -2949,14 +2943,15 @@ QDF_STATUS ol_txrx_clear_peer(uint8_t sta_id)
 }
 
 /**
- * ol_txrx_peer_detach - Delete a peer's data object.
- * @data_peer - the object to delete
+ * ol_txrx_peer_detach() - Delete a peer's data object.
+ * @peer - the object to detach
  *
  * When the host's control SW disassociates a peer, it calls
- * this function to delete the peer's data object. The reference
+ * this function to detach and delete the peer. The reference
  * stored in the control peer object to the data peer
  * object (set up by a call to ol_peer_store()) is provided.
  *
+ * Return: None
  */
 void ol_txrx_peer_detach(ol_txrx_peer_handle peer)
 {
@@ -2992,9 +2987,12 @@ void ol_txrx_peer_detach(ol_txrx_peer_handle peer)
 
 	qdf_spinlock_destroy(&peer->peer_info_lock);
 	qdf_spinlock_destroy(&peer->bufq_lock);
-	/* set delete_in_progress to identify that wma
-	 * is waiting for unmap massage for this peer */
+	/*
+	 * set delete_in_progress to identify that wma
+	 * is waiting for unmap massage for this peer
+	 */
 	qdf_atomic_set(&peer->delete_in_progress, 1);
+
 	/*
 	 * Remove the reference added during peer_attach.
 	 * The peer will still be left allocated until the
@@ -3004,6 +3002,40 @@ void ol_txrx_peer_detach(ol_txrx_peer_handle peer)
 	ol_txrx_peer_unref_delete(peer);
 }
 
+/**
+ * ol_txrx_peer_detach_force_delete() - Detach and delete a peer's data object
+ * @peer - the object to detach
+ *
+ * Detach a peer and force the peer object to be removed. It is called during
+ * roaming scenario when the firmware has already deleted a peer.
+ * Peer object is freed immediately to avoid duplicate peers during roam sync
+ * indication processing.
+ *
+ * Return: None
+ */
+void ol_txrx_peer_detach_force_delete(ol_txrx_peer_handle peer)
+{
+	ol_txrx_pdev_handle pdev = peer->vdev->pdev;
+
+	TXRX_PRINT(TXRX_PRINT_LEVEL_ERR, "%s peer %p, peer->ref_cnt %d",
+		__func__, peer, qdf_atomic_read(&peer->ref_cnt));
+
+	/* Clear the peer_id_to_obj map entries */
+	qdf_spin_lock_bh(&pdev->peer_map_unmap_lock);
+	ol_txrx_peer_remove_obj_map_entries(pdev, peer);
+	qdf_spin_unlock_bh(&pdev->peer_map_unmap_lock);
+
+	/*
+	 * Set ref_cnt = 1 so that ol_txrx_peer_unref_delete() called by
+	 * ol_txrx_peer_detach() will actually delete this peer entry properly.
+	 */
+	qdf_spin_lock_bh(&pdev->peer_ref_mutex);
+	qdf_atomic_set(&peer->ref_cnt, 1);
+	qdf_spin_unlock_bh(&pdev->peer_ref_mutex);
+
+	ol_txrx_peer_detach(peer);
+}
+
 ol_txrx_peer_handle
 ol_txrx_peer_find_by_addr(struct ol_txrx_pdev_t *pdev, uint8_t *peer_mac_addr)
 {

+ 1 - 0
core/dp/txrx/ol_txrx.h

@@ -134,4 +134,5 @@ void htt_pkt_log_init(struct ol_txrx_pdev_t *handle, void *scn);
 QDF_STATUS ol_txrx_set_wisa_mode(ol_txrx_vdev_handle vdev,
 			bool enable);
 void ol_txrx_update_mac_id(uint8_t vdev_id, uint8_t mac_id);
+void ol_txrx_peer_detach_force_delete(ol_txrx_peer_handle peer);
 #endif /* _OL_TXRX__H_ */

+ 117 - 17
core/dp/txrx/ol_txrx_peer_find.c

@@ -327,14 +327,27 @@ static void ol_txrx_peer_find_map_detach(struct ol_txrx_pdev_t *pdev)
 	qdf_mem_free(pdev->peer_id_to_obj_map);
 }
 
-static inline void
-ol_txrx_peer_find_add_id(struct ol_txrx_pdev_t *pdev,
-			 uint8_t *peer_mac_addr, uint16_t peer_id)
+/*
+ * ol_txrx_peer_find_add_id() - Add peer_id entry to peer
+ *
+ * @pdev: Handle to pdev object
+ * @peer_mac_addr: MAC address of peer provided by firmware
+ * @peer_id: peer_id provided by firmware
+ *
+ * Search for peer object for the MAC address, add the peer_id to
+ * its array of peer_id's and update the peer_id_to_obj map entry
+ * for that peer_id. Increment corresponding reference counts.
+ *
+ * Return: None
+ */
+static inline void ol_txrx_peer_find_add_id(struct ol_txrx_pdev_t *pdev,
+				uint8_t *peer_mac_addr, uint16_t peer_id)
 {
 	struct ol_txrx_peer_t *peer;
 	int status;
 	int del_peer_ref = 0;
 	int i;
+	bool found = false;
 
 	/* check if there's already a peer object with this MAC address */
 	peer =
@@ -366,10 +379,18 @@ ol_txrx_peer_find_add_id(struct ol_txrx_pdev_t *pdev,
 	qdf_atomic_inc
 		(&pdev->peer_id_to_obj_map[peer_id].peer_id_ref_cnt);
 
-	if (peer->peer_ids[0] == HTT_INVALID_PEER)
+	/* Check if entire peer_id array is empty */
+	for (i = 0; i < MAX_NUM_PEER_ID_PER_PEER; i++) {
+		if (peer->peer_ids[i] != HTT_INVALID_PEER) {
+			found = true;
+			break;
+		}
+	}
+	if (!found)
 		del_peer_ref = 1;
-
 	status = 1;
+
+	/* find a place in peer_id array and insert peer_id */
 	for (i = 0; i < MAX_NUM_PEER_ID_PER_PEER; i++) {
 		if (peer->peer_ids[i] == HTT_INVALID_PEER) {
 			peer->peer_ids[i] = peer_id;
@@ -506,7 +527,20 @@ void ol_txrx_peer_tx_ready_handler(ol_txrx_pdev_handle pdev, uint16_t peer_id)
 
 #endif
 
-
+/*
+ * ol_rx_peer_unmap_handler() - Handle peer unmap event from firmware
+ *
+ * @pdev: Handle to pdev pbject
+ * @peer_id: peer_id unmapped by firmware
+ *
+ * Decrement reference count for the peer_id in peer_id_to_obj_map,
+ * decrement reference count in corresponding peer object and clear the entry
+ * in peer's peer_ids array.
+ * In case of unmap events for a peer that is already deleted, just decrement
+ * del_peer_id_ref_cnt.
+ *
+ * Return: None
+ */
 void ol_rx_peer_unmap_handler(ol_txrx_pdev_handle pdev, uint16_t peer_id)
 {
 	struct ol_txrx_peer_t *peer;
@@ -518,24 +552,39 @@ void ol_rx_peer_unmap_handler(ol_txrx_pdev_handle pdev, uint16_t peer_id)
 		return;
 	}
 
-	qdf_spin_lock(&pdev->peer_map_unmap_lock);
+	qdf_spin_lock_bh(&pdev->peer_map_unmap_lock);
 
+	if (qdf_atomic_read(
+		&pdev->peer_id_to_obj_map[peer_id].del_peer_id_ref_cnt)) {
+		/* This peer_id belongs to a peer already deleted */
+		qdf_atomic_dec(&pdev->peer_id_to_obj_map[peer_id].
+					del_peer_id_ref_cnt);
+		TXRX_PRINT(TXRX_PRINT_LEVEL_ERR,
+			   "%s: Remove the ID %d reference to deleted peer. del_peer_id_ref_cnt %d",
+			   __func__, peer_id,
+			   qdf_atomic_read(&pdev->peer_id_to_obj_map[peer_id].
+							del_peer_id_ref_cnt));
+		qdf_spin_unlock_bh(&pdev->peer_map_unmap_lock);
+		return;
+	}
 	peer = pdev->peer_id_to_obj_map[peer_id].peer;
 
 	if (peer == NULL) {
-	/*
-	 * Currently peer IDs are assigned for vdevs as well as peers.
-	 * If the peer ID is for a vdev, then the peer pointer stored
-	 * in peer_id_to_obj_map will be NULL.
-	 */
-		qdf_spin_unlock(&pdev->peer_map_unmap_lock);
+		/*
+		 * Currently peer IDs are assigned for vdevs as well as peers.
+		 * If the peer ID is for a vdev, then the peer pointer stored
+		 * in peer_id_to_obj_map will be NULL.
+		 */
+		TXRX_PRINT(TXRX_PRINT_LEVEL_ERR,
+			   "%s: peer not found for peer_id %d",
+			   __func__, peer_id);
+		qdf_spin_unlock_bh(&pdev->peer_map_unmap_lock);
 		return;
 	}
 
 	if (qdf_atomic_dec_and_test
 		(&pdev->peer_id_to_obj_map[peer_id].peer_id_ref_cnt)) {
 		pdev->peer_id_to_obj_map[peer_id].peer = NULL;
-
 		for (i = 0; i < MAX_NUM_PEER_ID_PER_PEER; i++) {
 			if (peer->peer_ids[i] == peer_id) {
 				peer->peer_ids[i] = HTT_INVALID_PEER;
@@ -543,9 +592,6 @@ void ol_rx_peer_unmap_handler(ol_txrx_pdev_handle pdev, uint16_t peer_id)
 			}
 		}
 	}
-
-	qdf_spin_unlock(&pdev->peer_map_unmap_lock);
-
 	TXRX_PRINT(TXRX_PRINT_LEVEL_ERR,
 		   "%s: Remove the ID %d reference to peer %p peer_id_ref_cnt %d",
 		   __func__, peer_id, peer,
@@ -557,6 +603,59 @@ void ol_rx_peer_unmap_handler(ol_txrx_pdev_handle pdev, uint16_t peer_id)
 	 * If there are no more references, delete the peer object.
 	 */
 	ol_txrx_peer_unref_delete(peer);
+
+	qdf_spin_unlock_bh(&pdev->peer_map_unmap_lock);
+}
+
+/**
+ * ol_txrx_peer_remove_obj_map_entries() - Remove matching pdev peer map entries
+ * @pdev: pdev handle
+ * @peer: peer to be removed
+ *
+ * Return: None
+ */
+void ol_txrx_peer_remove_obj_map_entries(ol_txrx_pdev_handle pdev,
+					struct ol_txrx_peer_t *peer)
+{
+	int i;
+	uint16_t peer_id;
+	int32_t peer_id_ref_cnt;
+
+	for (i = 0; i < MAX_NUM_PEER_ID_PER_PEER; i++) {
+		peer_id = peer->peer_ids[i];
+		if (peer_id == HTT_INVALID_PEER ||
+			pdev->peer_id_to_obj_map[peer_id].peer == NULL) {
+			/* unused peer_id, or object is already dereferenced */
+			continue;
+		}
+		if (pdev->peer_id_to_obj_map[peer_id].peer != peer) {
+			QDF_TRACE(QDF_MODULE_ID_TXRX,
+				QDF_TRACE_LEVEL_ERROR,
+				FL("peer pointer mismatch in peer_id_to_obj"));
+			continue;
+		}
+		peer_id_ref_cnt = qdf_atomic_read(
+					&pdev->peer_id_to_obj_map[peer_id].
+						peer_id_ref_cnt);
+		QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_INFO_LOW,
+			  FL("peer_id = %d, peer_id_ref_cnt = %d, index = %d"),
+			  peer_id, peer_id_ref_cnt, i);
+		/*
+		 * Transfer peer_id_ref_cnt into del_peer_id_ref_cnt so that
+		 * ol_txrx_peer_unref_delete will decrement del_peer_id_ref_cnt
+		 * and any map events will increment peer_id_ref_cnt. Otherwise
+		 * accounting will be messed up.
+		 *
+		 * Add operation will ensure that back to back roaming in the
+		 * middle of unmap/map event sequence will be accounted for.
+		 */
+		qdf_atomic_add(peer_id_ref_cnt,
+			&pdev->peer_id_to_obj_map[peer_id].del_peer_id_ref_cnt);
+		qdf_atomic_init(&pdev->peer_id_to_obj_map[peer_id].
+				peer_id_ref_cnt);
+		pdev->peer_id_to_obj_map[peer_id].peer = NULL;
+		peer->peer_ids[i] = HTT_INVALID_PEER;
+	}
 }
 
 struct ol_txrx_peer_t *ol_txrx_assoc_peer_find(struct ol_txrx_vdev_t *vdev)
@@ -623,4 +722,5 @@ void ol_txrx_peer_find_display(ol_txrx_pdev_handle pdev, int indent)
 		}
 	}
 }
+
 #endif /* if TXRX_DEBUG_LEVEL */

+ 3 - 0
core/dp/txrx/ol_txrx_peer_find.h

@@ -107,6 +107,9 @@ void ol_txrx_peer_find_hash_erase(struct ol_txrx_pdev_t *pdev);
 
 struct ol_txrx_peer_t *ol_txrx_assoc_peer_find(struct ol_txrx_vdev_t *vdev);
 
+void ol_txrx_peer_remove_obj_map_entries(ol_txrx_pdev_handle pdev,
+					struct ol_txrx_peer_t *peer);
+
 #if defined(TXRX_DEBUG_LEVEL) && TXRX_DEBUG_LEVEL > 5
 void ol_txrx_peer_find_display(ol_txrx_pdev_handle pdev, int indent);
 #else

+ 11 - 1
core/dp/txrx/ol_txrx_types.h

@@ -58,7 +58,7 @@
  * multicast key the peer uses, and another ID to represent the
  * unicast key the peer uses.
  */
-#define MAX_NUM_PEER_ID_PER_PEER 8
+#define MAX_NUM_PEER_ID_PER_PEER 16
 
 #define OL_TXRX_INVALID_NUM_PEERS (-1)
 
@@ -484,9 +484,19 @@ struct ol_tx_flow_pool_t {
 
 #endif
 
+/*
+ * struct ol_txrx_peer_id_map - Map of firmware peer_ids to peers on host
+ * @peer: Pointer to peer object
+ * @peer_id_ref_cnt: No. of firmware references to the peer_id
+ * @del_peer_id_ref_cnt: No. of outstanding unmap events for peer_id
+ *                       after the peer object is deleted on the host.
+ *
+ * peer_id is used as an index into the array of ol_txrx_peer_id_map.
+ */
 struct ol_txrx_peer_id_map {
 	struct ol_txrx_peer_t *peer;
 	qdf_atomic_t peer_id_ref_cnt;
+	qdf_atomic_t del_peer_id_ref_cnt;
 };
 
 /*

+ 7 - 3
core/wma/src/wma_dev_if.c

@@ -1079,9 +1079,13 @@ void wma_remove_peer(tp_wma_handle wma, uint8_t *bssid,
 			wma->interfaces[vdev_id].peer_count);
 		return;
 	}
-	if (peer)
-		ol_txrx_peer_detach(peer);
 
+	if (peer) {
+		if (roam_synch_in_progress)
+			ol_txrx_peer_detach_force_delete(peer);
+		else
+			ol_txrx_peer_detach(peer);
+	}
 	peer_mac_addr = ol_txrx_peer_get_peer_mac_addr(peer);
 	if (peer_mac_addr == NULL) {
 		WMA_LOGE("%s: peer mac addr is NULL, Can't remove peer with peer_addr %pM vdevid %d peer_count %d",
@@ -1146,8 +1150,8 @@ QDF_STATUS wma_create_peer(tp_wma_handle wma, ol_txrx_pdev_handle pdev,
 		WMA_LOGE("%s : Unable to attach peer %pM", __func__, peer_addr);
 		goto err;
 	}
-	if (roam_synch_in_progress) {
 
+	if (roam_synch_in_progress) {
 		WMA_LOGE("%s: LFR3: Created peer %p with peer_addr %pM vdev_id %d,"
 			 "peer_count - %d",
 			 __func__, peer, peer_addr, vdev_id,