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
This commit is contained in:
Deepak Dhamdhere 2016-09-16 02:47:01 -07:00 • committed by qcabuildsw
commit f74d6f8a97
6 changed files with 186 additions and 36 deletions

View file

@ -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)
{

View file

@ -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_ */

View file

@ -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 */

View file

@ -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

View file

@ -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;
};
/*

View file

@ -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,