From 0696eef313b507aacbd7b3864a52869013851e5f Mon Sep 17 00:00:00 2001 From: Mohit Khanna Date: Thu, 14 Apr 2016 16:14:08 -0700 Subject: qcacld-3.0: Fix RX data, peer unmap race condition During peer unmap handler, while the peer is being deleted, there is a possible race condition if the OL Rx thread is processing RX packets and accesses the peer structure after its contents have been nulled. Remove race condition by - * Flush all RX packets in ol_txrx_peer_detach function which happens before peer unmap event is received from firmware * Avoid use of peer data structures (for example peer->local_id) outside of peer->info_lock in ol_rx_data_cb function. Use cached local copies of peer data structures instead Crash signature due to the race condition: wlan: [0:E :CDF] TXRX: Deleting peer ffffffc012fd13c0 (02:a0:c6:81:f8:c0) Unable to handle kernel paging request at virtual address 400000001 pgd = ffffffc0018b4000 [400000001] *pgd=0000000000000000, *pud=0000000000000000 Internal error: Oops: 96000005 [#1] PREEMPT SMP Modules linked in: wlan(O) [last unloaded: wlan] CPU: 1 PID: 29506 Comm: cds_ol_rx_threa Tainted: G W O 3.18.20-g5222edf-13780-g2219ed2 #1 Hardware name: Qualcomm Technologies, Inc. MSM 8996 v3 + PMI8996 CD (DT) task: ffffffc09350d400 ti: ffffffc0556a4000 task.ti: ffffffc0556a4000 PC is at hdd_rx_packet_cbk+0x84/0x224 [wlan] LR is at hdd_rx_packet_cbk+0x48/0x224 [wlan] pc : [] lr : [] pstate: 80000145 Change-Id: I4b32313024ec214f33dcdcfc401aadfa8af9d692 CRs-Fixed: 1002081 --- core/cds/inc/cds_sched.h | 65 +++++++++++++++++++++++++++- core/dp/txrx/ol_txrx.c | 109 ++++++++++++++++++++++++++--------------------- 2 files changed, 125 insertions(+), 49 deletions(-) diff --git a/core/cds/inc/cds_sched.h b/core/cds/inc/cds_sched.h index 7325b6155e26..46908e6701f6 100644 --- a/core/cds/inc/cds_sched.h +++ b/core/cds/inc/cds_sched.h @@ -296,7 +296,6 @@ typedef struct _cds_context_type { /*--------------------------------------------------------------------------- Function declarations and documenation ---------------------------------------------------------------------------*/ - #ifdef QCA_CONFIG_SMP /*--------------------------------------------------------------------------- \brief cds_drop_rxpkt_by_staid() - API to drop pending Rx packets for a sta @@ -357,6 +356,70 @@ void cds_free_ol_rx_pkt(p_cds_sched_context pSchedContext, \sa cds_free_ol_rx_pkt_freeq() -------------------------------------------------------------------------*/ void cds_free_ol_rx_pkt_freeq(p_cds_sched_context pSchedContext); +#else +/** + * cds_drop_rxpkt_by_staid() - api to drop pending rx packets for a sta + * @pSchedContext: Pointer to the global CDS Sched Context + * @staId: Station Id + * + * This api drops queued packets for a station, to drop all the pending + * packets the caller has to send WLAN_MAX_STA_COUNT as staId. + * + * Return: none + */ +static inline +void cds_drop_rxpkt_by_staid(p_cds_sched_context pSchedContext, uint16_t staId) +{ +} + +/** + * cds_indicate_rxpkt() - API to Indicate rx data packet + * @pSchedContext: pointer to CDS Sched Context + * @pkt: CDS OL RX pkt pointer containing to RX data message buffer + * + * Return: none + */ +static inline +void cds_indicate_rxpkt(p_cds_sched_context pSchedContext, + struct cds_ol_rx_pkt *pkt) +{ +} + +/** + * cds_alloc_ol_rx_pkt() - API to return next available cds message + * @pSchedContext: pointer to CDS Sched Context + * + * Return: none + */ +static inline +struct cds_ol_rx_pkt *cds_alloc_ol_rx_pkt(p_cds_sched_context pSchedContext) +{ +} + +/** + * cds_free_ol_rx_pkt() - API to release cds message to the freeq + * @pSchedContext: pointer to CDS Sched Context + * @pkt: CDS message buffer to be returned to free queue + * + * Return: none + */ +static inline +void cds_free_ol_rx_pkt(p_cds_sched_context pSchedContext, + struct cds_ol_rx_pkt *pkt) +{ +} + +/** + * cds_free_ol_rx_pkt_freeq() - Free cds buffer free queue + * @pSchedContext: pointer to CDS Sched Context + * @pkt: CDS message buffer to be returned to free queue + * + * Return: none + */ +static inline +void cds_free_ol_rx_pkt_freeq(p_cds_sched_context pSchedContext) +{ +} #endif /*--------------------------------------------------------------------------- diff --git a/core/dp/txrx/ol_txrx.c b/core/dp/txrx/ol_txrx.c index 2316b1e5cd4f..c2d85772e6df 100644 --- a/core/dp/txrx/ol_txrx.c +++ b/core/dp/txrx/ol_txrx.c @@ -2226,6 +2226,62 @@ void ol_txrx_peer_unref_delete(ol_txrx_peer_handle peer) } } +/** + * ol_txrx_clear_peer_internal() - ol internal function to clear peer + * @peer: pointer to ol txrx peer structure + * + * Return: QDF Status + */ +static QDF_STATUS +ol_txrx_clear_peer_internal(struct ol_txrx_peer_t *peer) +{ + p_cds_sched_context sched_ctx = get_cds_sched_ctxt(); + /* Drop pending Rx frames in CDS */ + if (sched_ctx) + cds_drop_rxpkt_by_staid(sched_ctx, peer->local_id); + + /* Purge the cached rx frame queue */ + ol_txrx_flush_rx_frames(peer, 1); + + qdf_spin_lock_bh(&peer->peer_info_lock); + peer->vdev->rx = NULL; + peer->state = OL_TXRX_PEER_STATE_DISC; + qdf_spin_unlock_bh(&peer->peer_info_lock); + + return QDF_STATUS_SUCCESS; +} + +/** + * ol_txrx_clear_peer() - clear peer + * @sta_id: sta id + * + * Return: QDF Status + */ +QDF_STATUS ol_txrx_clear_peer(uint8_t sta_id) +{ + struct ol_txrx_peer_t *peer; + struct ol_txrx_pdev_t *pdev = cds_get_context(QDF_MODULE_ID_TXRX); + + if (!pdev) { + TXRX_PRINT(TXRX_PRINT_LEVEL_ERR, "%s: Unable to find pdev!", + __func__); + return QDF_STATUS_E_FAILURE; + } + + if (sta_id >= WLAN_MAX_STA_COUNT) { + TXRX_PRINT(TXRX_PRINT_LEVEL_ERR, "Invalid sta id %d", sta_id); + return QDF_STATUS_E_INVAL; + } + + + peer = ol_txrx_peer_find_by_local_id(pdev, sta_id); + if (!peer) + return QDF_STATUS_E_FAULT; + + return ol_txrx_clear_peer_internal(peer); + +} + /** * ol_txrx_peer_detach - Delete a peer's data object. * @data_peer - the object to delete @@ -2245,6 +2301,8 @@ void ol_txrx_peer_detach(ol_txrx_peer_handle peer) peer->valid = 0; + /* flush all rx packets before clearing up the peer local_id */ + ol_txrx_clear_peer_internal(peer); ol_txrx_local_peer_id_free(peer->vdev->pdev, peer); /* debug print to dump rx reorder state */ @@ -2256,7 +2314,6 @@ void ol_txrx_peer_detach(ol_txrx_peer_handle 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]); - ol_txrx_flush_rx_frames(peer, 1); if (peer->vdev->last_real_peer == peer) peer->vdev->last_real_peer = NULL; @@ -3455,6 +3512,7 @@ static void ol_rx_data_cb(struct ol_txrx_pdev_t *pdev, qdf_nbuf_t buf_list, uint16_t staid) { void *cds_ctx = cds_get_global_context(); + void *osif_dev; qdf_nbuf_t buf, next_buf; QDF_STATUS ret; ol_txrx_rx_fp data_rx = NULL; @@ -3478,6 +3536,7 @@ static void ol_rx_data_cb(struct ol_txrx_pdev_t *pdev, } data_rx = peer->vdev->rx; + osif_dev = peer->vdev->osif_dev; qdf_spin_unlock_bh(&peer->peer_info_lock); qdf_spin_lock_bh(&peer->bufq_lock); @@ -3492,7 +3551,7 @@ static void ol_rx_data_cb(struct ol_txrx_pdev_t *pdev, while (buf) { next_buf = qdf_nbuf_queue_next(buf); qdf_nbuf_set_next(buf, NULL); /* Add NULL terminator */ - ret = data_rx(peer->vdev->osif_dev, buf); + ret = data_rx(osif_dev, buf); if (ret != QDF_STATUS_SUCCESS) { TXRX_PRINT(TXRX_PRINT_LEVEL_ERR, "Frame Rx to HDD failed"); qdf_nbuf_free(buf); @@ -3660,52 +3719,6 @@ QDF_STATUS ol_txrx_register_peer(struct ol_txrx_desc_type *sta_desc) return QDF_STATUS_SUCCESS; } -/** - * ol_txrx_clear_peer() - clear peer - * @sta_id: sta id - * - * Return: QDF Status - */ -QDF_STATUS ol_txrx_clear_peer(uint8_t sta_id) -{ - struct ol_txrx_peer_t *peer; - struct ol_txrx_pdev_t *pdev = cds_get_context(QDF_MODULE_ID_TXRX); - - if (!pdev) { - TXRX_PRINT(TXRX_PRINT_LEVEL_ERR, "%s: Unable to find pdev!", - __func__); - return QDF_STATUS_E_FAILURE; - } - - if (sta_id >= WLAN_MAX_STA_COUNT) { - TXRX_PRINT(TXRX_PRINT_LEVEL_ERR, "Invalid sta id %d", sta_id); - return QDF_STATUS_E_INVAL; - } - -#ifdef QCA_CONFIG_SMP - { - p_cds_sched_context sched_ctx = get_cds_sched_ctxt(); - /* Drop pending Rx frames in CDS */ - if (sched_ctx) - cds_drop_rxpkt_by_staid(sched_ctx, sta_id); - } -#endif - - peer = ol_txrx_peer_find_by_local_id(pdev, sta_id); - if (!peer) - return QDF_STATUS_E_FAULT; - - /* Purge the cached rx frame queue */ - ol_txrx_flush_rx_frames(peer, 1); - - qdf_spin_lock_bh(&peer->peer_info_lock); - peer->vdev->rx = NULL; - peer->state = OL_TXRX_PEER_STATE_DISC; - qdf_spin_unlock_bh(&peer->peer_info_lock); - - return QDF_STATUS_SUCCESS; -} - /** * ol_txrx_register_ocb_peer - Function to register the OCB peer * @cds_ctx: Pointer to the global OS context -- cgit v1.2.3