| /* |
| * Copyright (c) 2012-2017 The Linux Foundation. All rights reserved. |
| * |
| * Previously licensed under the ISC license by Qualcomm Atheros, Inc. |
| * |
| * |
| * Permission to use, copy, modify, and/or distribute this software for |
| * any purpose with or without fee is hereby granted, provided that the |
| * above copyright notice and this permission notice appear in all |
| * copies. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL |
| * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE |
| * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL |
| * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR |
| * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER |
| * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR |
| * PERFORMANCE OF THIS SOFTWARE. |
| */ |
| |
| /* |
| * This file was originally distributed by Qualcomm Atheros, Inc. |
| * under proprietary terms before Copyright ownership was assigned |
| * to the Linux Foundation. |
| */ |
| |
| #include <adf_nbuf.h> /* adf_nbuf_t, etc. */ |
| #include <adf_os_atomic.h> /* adf_os_atomic_add, etc. */ |
| #include <ol_cfg.h> /* ol_cfg_addba_retry */ |
| #include <htt.h> /* HTT_TX_EXT_TID_MGMT */ |
| #include <ol_htt_tx_api.h> /* htt_tx_desc_tid */ |
| #include <ol_txrx_api.h> /* ol_txrx_vdev_handle */ |
| #include <ol_txrx_ctrl_api.h> /* ol_txrx_sync, ol_tx_addba_conf */ |
| #include <ol_ctrl_txrx_api.h> /* ol_ctrl_addba_req */ |
| #include <ol_txrx_internal.h> /* TXRX_ASSERT1, etc. */ |
| #include <ol_txrx_types.h> /* pdev stats */ |
| #include <ol_tx_desc.h> /* ol_tx_desc, ol_tx_desc_frame_list_free */ |
| #include <ol_tx.h> /* ol_tx_vdev_ll_pause_queue_send */ |
| #include <ol_tx_sched.h> /* ol_tx_sched_notify, etc. */ |
| #include <ol_tx_queue.h> |
| #include <ol_txrx_dbg.h> /* DEBUG_HL_LOGGING */ |
| #include <ol_txrx.h> /* ol_tx_desc_pool_size_hl */ |
| #include <adf_os_types.h> /* a_bool_t */ |
| #include <ol_txrx_peer_find.h> |
| #include "adf_trace.h" |
| |
| |
| #if defined(CONFIG_HL_SUPPORT) |
| |
| #ifndef offsetof |
| #define offsetof(type, field) ((adf_os_size_t)(&((type *)0)->field)) |
| #endif |
| |
| /*--- function prototypes for optional queue log feature --------------------*/ |
| #if defined(DEBUG_HL_LOGGING) |
| |
| void |
| ol_tx_queue_log_enqueue( |
| struct ol_txrx_pdev_t *pdev, |
| struct ol_txrx_msdu_info_t *msdu_info, |
| int frms, int bytes); |
| void |
| ol_tx_queue_log_dequeue( |
| struct ol_txrx_pdev_t *pdev, |
| struct ol_tx_frms_queue_t *txq, |
| int frms, int bytes); |
| void |
| ol_tx_queue_log_free( |
| struct ol_txrx_pdev_t *pdev, |
| struct ol_tx_frms_queue_t *txq, |
| int tid, int frms, int bytes, bool is_peer_txq); |
| #define OL_TX_QUEUE_LOG_ENQUEUE ol_tx_queue_log_enqueue |
| #define OL_TX_QUEUE_LOG_DEQUEUE ol_tx_queue_log_dequeue |
| #define OL_TX_QUEUE_LOG_FREE ol_tx_queue_log_free |
| |
| #else |
| |
| #define OL_TX_QUEUE_LOG_ENQUEUE(pdev, msdu_info, frms, bytes) /* no-op */ |
| #define OL_TX_QUEUE_LOG_DEQUEUE(pdev, txq, frms, bytes) /* no-op */ |
| /* no-op */ |
| #define OL_TX_QUEUE_LOG_FREE(pdev, txq, tid, frms, bytes, is_peer_txq) |
| |
| #endif /* TXRX_DEBUG_LEVEL > 5 */ |
| |
| |
| /*--- function prototypes for optional host ADDBA negotiation ---------------*/ |
| |
| #define OL_TX_QUEUE_ADDBA_CHECK(pdev, txq, tx_msdu_info) /* no-op */ |
| |
| |
| #ifndef container_of |
| #define container_of(ptr, type, member) ((type *)( \ |
| (char *)(ptr) - (char *)(&((type *)0)->member) ) ) |
| #endif |
| /*--- function definitions --------------------------------------------------*/ |
| |
| /* |
| * Try to flush pending frames in the tx queues |
| * no matter it's queued in the TX scheduler or not. |
| */ |
| static inline void |
| ol_tx_queue_vdev_flush(struct ol_txrx_pdev_t *pdev, struct ol_txrx_vdev_t *vdev) |
| { |
| #define PEER_ARRAY_COUNT 10 |
| struct ol_tx_frms_queue_t *txq; |
| struct ol_txrx_peer_t *peer, *peers[PEER_ARRAY_COUNT]; |
| int i, j, peer_count; |
| |
| /* flush bundling queue */ |
| ol_tx_hl_queue_flush_all(vdev); |
| |
| /* flush del_ack queue */ |
| ol_tx_hl_del_ack_queue_flush_all(vdev); |
| |
| /* flush VDEV TX queues */ |
| for (i = 0; i < OL_TX_VDEV_NUM_QUEUES; i++) { |
| txq = &vdev->txqs[i]; |
| /* |
| * currently txqs of MCAST_BCAST/DEFAULT_MGMT packet are using tid |
| * HTT_TX_EXT_TID_NON_QOS_MCAST_BCAST/HTT_TX_EXT_TID_MGMT when inserted |
| * into scheduler, so use same tid when we flush them |
| */ |
| if (i == OL_TX_VDEV_MCAST_BCAST) |
| ol_tx_queue_free(pdev, txq, HTT_TX_EXT_TID_NON_QOS_MCAST_BCAST, |
| false); |
| else if (i == OL_TX_VDEV_DEFAULT_MGMT) |
| ol_tx_queue_free(pdev, txq, HTT_TX_EXT_TID_MGMT, false); |
| else |
| ol_tx_queue_free(pdev, txq, (i + OL_TX_NUM_TIDS), false); |
| } |
| /* flush PEER TX queues */ |
| do { |
| peer_count = 0; |
| /* select candidate peers */ |
| adf_os_spin_lock_bh(&pdev->peer_ref_mutex); |
| TAILQ_FOREACH(peer, &vdev->peer_list, peer_list_elem) { |
| for (i = 0; i < OL_TX_NUM_TIDS; i++) { |
| txq = &peer->txqs[i]; |
| if (txq->frms) { |
| adf_os_atomic_inc(&peer->ref_cnt); |
| peers[peer_count++] = peer; |
| break; |
| } |
| } |
| if (peer_count >= PEER_ARRAY_COUNT) { |
| break; |
| } |
| } |
| adf_os_spin_unlock_bh(&pdev->peer_ref_mutex); |
| /* flush TX queues of candidate peers */ |
| for (i = 0; i < peer_count; i++) { |
| for (j = 0; j < OL_TX_NUM_TIDS; j++) { |
| txq = &peers[i]->txqs[j]; |
| if (txq->frms) { |
| ol_tx_queue_free(pdev, txq, j, true); |
| } |
| } |
| TXRX_PRINT(TXRX_PRINT_LEVEL_ERR, |
| "%s: Delete Peer %pK\n", __func__, peer); |
| ol_txrx_peer_unref_delete(peers[i]); |
| } |
| } while (peer_count >= PEER_ARRAY_COUNT); |
| } |
| |
| static inline void |
| ol_tx_queue_flush(struct ol_txrx_pdev_t *pdev) |
| { |
| struct ol_txrx_vdev_t *vdev; |
| |
| TAILQ_FOREACH(vdev, &pdev->vdev_list, vdev_list_elem) { |
| ol_tx_queue_vdev_flush(pdev, vdev); |
| } |
| } |
| |
| void |
| ol_tx_queue_discard( |
| struct ol_txrx_pdev_t *pdev, |
| a_bool_t flush_all, |
| ol_tx_desc_list *tx_descs) |
| { |
| u_int16_t num; |
| u_int16_t discarded, actual_discarded = 0; |
| |
| adf_os_spin_lock_bh(&pdev->tx_queue_spinlock); |
| |
| if (flush_all == A_TRUE) { |
| /* flush all the pending tx queues in the scheduler */ |
| num = ol_tx_desc_pool_size_hl(pdev->ctrl_pdev) - |
| adf_os_atomic_read(&pdev->tx_queue.rsrc_cnt); |
| } else { |
| num = pdev->tx_queue.rsrc_threshold_hi - |
| pdev->tx_queue.rsrc_threshold_lo; |
| } |
| TX_SCHED_DEBUG_PRINT("+%s : %u\n,", __FUNCTION__, |
| adf_os_atomic_read(&pdev->tx_queue.rsrc_cnt)); |
| while (num > 0) { |
| discarded = ol_tx_sched_discard_select( |
| pdev, (u_int16_t)num, tx_descs, flush_all); |
| if (discarded == 0) { |
| /* |
| * No more packets could be discarded. |
| * Probably tx queues are empty. |
| */ |
| break; |
| } |
| num -= discarded; |
| actual_discarded += discarded; |
| } |
| adf_os_atomic_add(actual_discarded, &pdev->tx_queue.rsrc_cnt); |
| TX_SCHED_DEBUG_PRINT("-%s \n",__FUNCTION__); |
| |
| adf_os_spin_unlock_bh(&pdev->tx_queue_spinlock); |
| |
| if (flush_all == A_TRUE && num > 0) { |
| /* |
| * try to flush pending frames in the tx queues |
| * which are not queued in the TX scheduler. |
| */ |
| ol_tx_queue_flush(pdev); |
| } |
| } |
| |
| void |
| ol_tx_enqueue( |
| struct ol_txrx_pdev_t *pdev, |
| struct ol_tx_frms_queue_t *txq, |
| struct ol_tx_desc_t *tx_desc, |
| struct ol_txrx_msdu_info_t *tx_msdu_info) |
| { |
| int bytes; |
| struct ol_tx_sched_notify_ctx_t notify_ctx; |
| #if defined(CONFIG_PER_VDEV_TX_DESC_POOL) |
| ol_txrx_vdev_handle vdev; |
| #endif |
| |
| TX_SCHED_DEBUG_PRINT("Enter %s\n", __func__); |
| |
| /* |
| * If too few tx descriptors are available, drop some currently-queued |
| * tx frames, to provide enough tx descriptors for new frames, which |
| * may be higher priority than the current frames. |
| */ |
| #if defined(CONFIG_PER_VDEV_TX_DESC_POOL) |
| vdev = tx_desc->vdev; |
| if (adf_os_atomic_read(&vdev->tx_desc_count) > |
| ((ol_tx_desc_pool_size_hl(pdev->ctrl_pdev) >> 1) |
| - TXRX_HL_TX_FLOW_CTRL_MGMT_RESERVED)) { |
| #else |
| if (adf_os_atomic_read(&pdev->tx_queue.rsrc_cnt) <= |
| pdev->tx_queue.rsrc_threshold_lo) |
| { |
| #endif |
| ol_tx_desc_list tx_descs; |
| TAILQ_INIT(&tx_descs); |
| ol_tx_queue_discard(pdev, A_FALSE, &tx_descs); |
| //Discard Frames in Discard List |
| ol_tx_desc_frame_list_free(pdev, &tx_descs, 1 /* error */); |
| } |
| adf_os_spin_lock_bh(&pdev->tx_queue_spinlock); |
| TAILQ_INSERT_TAIL(&txq->head, tx_desc, tx_desc_list_elem); |
| |
| bytes = adf_nbuf_len(tx_desc->netbuf); |
| txq->frms++; |
| txq->bytes += bytes; |
| OL_TX_QUEUE_LOG_ENQUEUE(pdev, tx_msdu_info, 1, bytes); |
| |
| if (txq->flag != ol_tx_queue_paused) { |
| notify_ctx.event = OL_TX_ENQUEUE_FRAME; |
| notify_ctx.frames = 1; |
| notify_ctx.bytes = adf_nbuf_len(tx_desc->netbuf); |
| notify_ctx.txq = txq; |
| notify_ctx.info.tx_msdu_info = tx_msdu_info; |
| ol_tx_sched_notify(pdev, ¬ify_ctx); |
| txq->flag = ol_tx_queue_active; |
| } |
| |
| if (!ETHERTYPE_IS_EAPOL_WAPI(tx_msdu_info->htt.info.ethertype)) { |
| OL_TX_QUEUE_ADDBA_CHECK(pdev, txq, tx_msdu_info); |
| } |
| adf_os_spin_unlock_bh(&pdev->tx_queue_spinlock); |
| TX_SCHED_DEBUG_PRINT("Leave %s\n", __func__); |
| } |
| |
| u_int16_t |
| ol_tx_dequeue( |
| struct ol_txrx_pdev_t *pdev, |
| struct ol_tx_frms_queue_t *txq, |
| ol_tx_desc_list *head, |
| u_int16_t max_frames, |
| u_int32_t *credit, |
| int *bytes) |
| { |
| u_int16_t num_frames; |
| int bytes_sum; |
| unsigned credit_sum; |
| u_int16_t temp_frms; |
| u_int32_t temp_bytes; |
| bool flush_all = false; |
| struct ol_tx_desc_t *tx_flush_desc; |
| |
| TXRX_ASSERT2(txq->flag != ol_tx_queue_paused); |
| TX_SCHED_DEBUG_PRINT("Enter %s\n", __func__); |
| temp_frms = txq->frms; |
| temp_bytes = txq->bytes; |
| |
| if (txq->frms < max_frames) { |
| max_frames = txq->frms; |
| } |
| bytes_sum = 0; |
| credit_sum = 0; |
| for (num_frames = 0; num_frames < max_frames; num_frames++) { |
| unsigned frame_credit; |
| struct ol_tx_desc_t *tx_desc; |
| tx_desc = TAILQ_FIRST(&txq->head); |
| if(!tx_desc) { |
| flush_all = true; |
| TXRX_PRINT(TXRX_PRINT_LEVEL_ERR, |
| "%s: flush frames: num_frames = %d, max_frames = %d\n", |
| __func__, num_frames, max_frames); |
| break; |
| } |
| |
| frame_credit = htt_tx_msdu_credit(tx_desc->netbuf); |
| if (credit_sum + frame_credit > *credit) { |
| break; |
| } |
| credit_sum += frame_credit; |
| bytes_sum += adf_nbuf_len(tx_desc->netbuf); |
| TAILQ_REMOVE(&txq->head, tx_desc, tx_desc_list_elem); |
| TAILQ_INSERT_TAIL(head, tx_desc, tx_desc_list_elem); |
| } |
| txq->frms -= num_frames; |
| txq->bytes -= bytes_sum; |
| /* a paused queue remains paused, regardless of whether it has frames */ |
| if (txq->frms == 0 && txq->flag == ol_tx_queue_active) { |
| txq->flag = ol_tx_queue_empty; |
| } |
| OL_TX_QUEUE_LOG_DEQUEUE(pdev, txq, num_frames, bytes_sum); |
| TX_SCHED_DEBUG_PRINT("Leave %s\n", __func__); |
| |
| if (flush_all && bytes_sum) { |
| *bytes = temp_bytes; |
| *credit = 0; |
| txq->frms = 0; |
| txq->bytes = 0; |
| while (num_frames) { |
| tx_flush_desc = TAILQ_FIRST(head); |
| TAILQ_REMOVE(head, tx_flush_desc, tx_desc_list_elem); |
| ol_tx_desc_frame_free_nonstd(pdev, tx_flush_desc, 0); |
| num_frames--; |
| } |
| |
| } else { |
| *bytes = bytes_sum; |
| *credit = credit_sum; |
| } |
| return num_frames; |
| } |
| |
| void |
| ol_tx_queue_free( |
| struct ol_txrx_pdev_t *pdev, |
| struct ol_tx_frms_queue_t *txq, |
| int tid, bool is_peer_txq) |
| { |
| int frms = 0, bytes = 0; |
| struct ol_tx_desc_t *tx_desc; |
| struct ol_tx_sched_notify_ctx_t notify_ctx; |
| ol_tx_desc_list tx_tmp_list; |
| |
| TAILQ_INIT(&tx_tmp_list); |
| TX_SCHED_DEBUG_PRINT("Enter %s\n", __func__); |
| adf_os_spin_lock_bh(&pdev->tx_queue_spinlock); |
| |
| notify_ctx.event = OL_TX_DELETE_QUEUE; |
| notify_ctx.txq = txq; |
| notify_ctx.info.ext_tid = tid; |
| ol_tx_sched_notify(pdev, ¬ify_ctx); |
| |
| frms = txq->frms; |
| tx_desc = TAILQ_FIRST(&txq->head); |
| while (txq->frms) { |
| bytes += adf_nbuf_len(tx_desc->netbuf); |
| txq->frms--; |
| tx_desc = TAILQ_NEXT(tx_desc, tx_desc_list_elem); |
| } |
| OL_TX_QUEUE_LOG_FREE(pdev, txq, tid, frms, bytes, is_peer_txq); |
| txq->bytes -= bytes; |
| OL_TX_QUEUE_LOG_FREE(pdev, txq, tid, frms, bytes, is_peer_txq); |
| txq->flag = ol_tx_queue_empty; |
| /* txq->head gets reset during the TAILQ_CONCAT call */ |
| TAILQ_CONCAT(&tx_tmp_list, &txq->head, tx_desc_list_elem); |
| |
| adf_os_spin_unlock_bh(&pdev->tx_queue_spinlock); |
| /* free tx frames without holding tx_queue_spinlock */ |
| adf_os_atomic_add(frms, &pdev->tx_queue.rsrc_cnt); |
| while (frms) { |
| tx_desc = TAILQ_FIRST(&tx_tmp_list); |
| TAILQ_REMOVE(&tx_tmp_list, tx_desc, tx_desc_list_elem); |
| ol_tx_desc_frame_free_nonstd(pdev, tx_desc, 0); |
| frms--; |
| } |
| TX_SCHED_DEBUG_PRINT("Leave %s\n", __func__); |
| } |
| |
| |
| /*--- queue pause / unpause functions ---------------------------------------*/ |
| |
| static inline void |
| ol_txrx_peer_tid_pause_base( |
| struct ol_txrx_pdev_t *pdev, |
| struct ol_txrx_peer_t *peer, |
| int tid) |
| { |
| struct ol_tx_frms_queue_t *txq = &peer->txqs[tid]; |
| |
| if (txq->paused_count.total++ == 0) { |
| struct ol_tx_sched_notify_ctx_t notify_ctx; |
| |
| notify_ctx.event = OL_TX_PAUSE_QUEUE; |
| notify_ctx.txq = txq; |
| notify_ctx.info.ext_tid = tid; |
| ol_tx_sched_notify(pdev, ¬ify_ctx); |
| txq->flag = ol_tx_queue_paused; |
| } |
| } |
| |
| static inline void |
| ol_txrx_peer_pause_but_no_mgmt_q_base( |
| struct ol_txrx_pdev_t *pdev, |
| struct ol_txrx_peer_t *peer) |
| { |
| int i; |
| for (i = 0; i < OL_TX_MGMT_TID; i++) { |
| ol_txrx_peer_tid_pause_base(pdev, peer, i); |
| } |
| } |
| |
| static inline void |
| ol_txrx_peer_pause_base( |
| struct ol_txrx_pdev_t *pdev, |
| struct ol_txrx_peer_t *peer) |
| { |
| int i; |
| for (i = 0; i < ARRAY_LEN(peer->txqs); i++) { |
| ol_txrx_peer_tid_pause_base(pdev, peer, i); |
| } |
| } |
| |
| static inline void |
| ol_txrx_peer_tid_unpause_base( |
| struct ol_txrx_pdev_t *pdev, |
| struct ol_txrx_peer_t *peer, |
| int tid) |
| { |
| struct ol_tx_frms_queue_t *txq = &peer->txqs[tid]; |
| /* |
| * Don't actually unpause the tx queue until all pause requests |
| * have been removed. |
| */ |
| TXRX_ASSERT2(txq->paused_count.total > 0); |
| /* return, if not already paused */ |
| if (txq->paused_count.total == 0) |
| return; |
| |
| if (--txq->paused_count.total == 0) { |
| struct ol_tx_sched_notify_ctx_t notify_ctx; |
| |
| notify_ctx.event = OL_TX_UNPAUSE_QUEUE; |
| notify_ctx.txq = txq; |
| notify_ctx.info.ext_tid = tid; |
| ol_tx_sched_notify(pdev, ¬ify_ctx); |
| |
| if (txq->frms == 0) { |
| txq->flag = ol_tx_queue_empty; |
| } else { |
| txq->flag = ol_tx_queue_active; |
| /* |
| * Now that the are new tx frames available to download, |
| * invoke the scheduling function, to see if it wants to |
| * download the new frames. |
| * Since the queue lock is currently held, and since |
| * the scheduler function takes the lock, temporarily |
| * release the lock. |
| */ |
| adf_os_spin_unlock_bh(&pdev->tx_queue_spinlock); |
| ol_tx_sched(pdev); |
| adf_os_spin_lock_bh(&pdev->tx_queue_spinlock); |
| } |
| } |
| } |
| |
| static inline void |
| ol_txrx_peer_unpause_but_no_mgmt_q_base( |
| struct ol_txrx_pdev_t *pdev, |
| struct ol_txrx_peer_t *peer) |
| { |
| int i; |
| for (i = 0; i < OL_TX_MGMT_TID; i++) { |
| ol_txrx_peer_tid_unpause_base(pdev, peer, i); |
| } |
| } |
| |
| void |
| ol_txrx_peer_tid_unpause(ol_txrx_peer_handle peer, int tid) |
| { |
| struct ol_txrx_pdev_t *pdev = peer->vdev->pdev; |
| |
| /* TO DO: log the queue unpause */ |
| |
| /* acquire the mutex lock, since we'll be modifying the queues */ |
| TX_SCHED_DEBUG_PRINT("Enter %s\n", __func__); |
| adf_os_spin_lock_bh(&pdev->tx_queue_spinlock); |
| |
| if (tid == -1) { |
| int i; |
| for (i = 0; i < ARRAY_LEN(peer->txqs); i++) { |
| ol_txrx_peer_tid_unpause_base(pdev, peer, i); |
| } |
| } else { |
| ol_txrx_peer_tid_unpause_base(pdev, peer, tid); |
| } |
| |
| adf_os_spin_unlock_bh(&pdev->tx_queue_spinlock); |
| TX_SCHED_DEBUG_PRINT("Leave %s\n", __func__); |
| } |
| |
| void |
| ol_txrx_throttle_pause(ol_txrx_pdev_handle pdev) |
| { |
| #if defined(QCA_SUPPORT_TX_THROTTLE) |
| adf_os_spin_lock_bh(&pdev->tx_throttle.mutex); |
| |
| if (pdev->tx_throttle.is_paused == TRUE) { |
| adf_os_spin_unlock_bh(&pdev->tx_throttle.mutex); |
| return; |
| } |
| |
| pdev->tx_throttle.is_paused = TRUE; |
| adf_os_spin_unlock_bh(&pdev->tx_throttle.mutex); |
| #endif |
| ol_txrx_pdev_pause(pdev, OL_TXQ_PAUSE_REASON_THROTTLE); |
| } |
| |
| void |
| ol_txrx_throttle_unpause(ol_txrx_pdev_handle pdev) |
| { |
| #if defined(QCA_SUPPORT_TX_THROTTLE) |
| adf_os_spin_lock_bh(&pdev->tx_throttle.mutex); |
| |
| if (pdev->tx_throttle.is_paused == FALSE) { |
| adf_os_spin_unlock_bh(&pdev->tx_throttle.mutex); |
| return; |
| } |
| |
| pdev->tx_throttle.is_paused = FALSE; |
| adf_os_spin_unlock_bh(&pdev->tx_throttle.mutex); |
| #endif |
| ol_txrx_pdev_unpause(pdev, OL_TXQ_PAUSE_REASON_THROTTLE); |
| } |
| #endif /* defined(CONFIG_HL_SUPPORT) */ |
| |
| #if defined(CONFIG_HL_SUPPORT) || defined(QCA_SUPPORT_TXRX_VDEV_PAUSE_LL) |
| /** |
| * ol_txrx_pdev_pause() - Suspend all tx data for the specified physical device. |
| * @data_pdev: the physical device being paused. |
| * @reason: pause reason. |
| * |
| * This function applies to HL systems - |
| * in LL systems, applies when txrx_vdev_pause_all is enabled. |
| * In some systems it is necessary to be able to temporarily |
| * suspend all WLAN traffic, e.g. to allow another device such as bluetooth |
| * to temporarily have exclusive access to shared RF chain resources. |
| * This function suspends tx traffic within the specified physical device. |
| * |
| * |
| * Return: None |
| */ |
| void |
| ol_txrx_pdev_pause(ol_txrx_pdev_handle pdev, u_int32_t reason) |
| { |
| struct ol_txrx_vdev_t *vdev = NULL, *tmp; |
| |
| TAILQ_FOREACH_SAFE(vdev, &pdev->vdev_list, vdev_list_elem, tmp) { |
| ol_txrx_vdev_pause(vdev, reason); |
| } |
| } |
| |
| /** |
| * ol_txrx_pdev_unpause() - Resume tx for the specified physical device.. |
| * @data_pdev: the physical device being paused. |
| * @reason: pause reason. |
| * |
| * This function applies to HL systems - |
| * in LL systems, applies when txrx_vdev_pause_all is enabled. |
| * |
| * |
| * Return: None |
| */ |
| void |
| ol_txrx_pdev_unpause(ol_txrx_pdev_handle pdev, u_int32_t reason) |
| { |
| struct ol_txrx_vdev_t *vdev = NULL, *tmp; |
| |
| TAILQ_FOREACH_SAFE(vdev, &pdev->vdev_list, vdev_list_elem, tmp) { |
| ol_txrx_vdev_unpause(vdev, reason); |
| } |
| } |
| |
| #if defined(CONFIG_HL_SUPPORT) |
| /** |
| * ol_txrx_pdev_pause_other_vdev() - Suspend all tx data for the specified physical device except |
| * current vdev. |
| * @data_pdev: the physical device being paused. |
| * @reason: pause reason. |
| * One can provide multiple line descriptions |
| * for arguments. |
| * @current_id: do not pause this vdev id queues |
| * |
| * This function applies to HL systems - |
| * in LL systems, applies when txrx_vdev_pause_all is enabled. |
| * In some cases it is necessary to be able to temporarily |
| * suspend other vdevs traffic, e.g. to avoid current EAPOL frames credit starvation |
| * |
| * Return: None |
| */ |
| void |
| ol_txrx_pdev_pause_other_vdev(ol_txrx_pdev_handle pdev, u_int32_t reason, u_int32_t current_id) |
| { |
| struct ol_txrx_vdev_t *vdev = NULL, *tmp; |
| |
| TAILQ_FOREACH_SAFE(vdev, &pdev->vdev_list, vdev_list_elem, tmp) { |
| if (vdev->vdev_id != current_id) { |
| ol_txrx_vdev_pause(vdev, reason); |
| } |
| } |
| } |
| |
| /** |
| * ol_txrx_pdev_unpause_other_vdev() - Resume tx for the paused vdevs.. |
| * @data_pdev: the physical device being paused. |
| * @reason: pause reason. |
| * @current_id: do not unpause this vdev |
| * |
| * This function applies to HL systems - |
| * in LL systems, applies when txrx_vdev_pause_all is enabled. |
| * |
| * |
| * Return: None |
| */ |
| void |
| ol_txrx_pdev_unpause_other_vdev(ol_txrx_pdev_handle pdev, u_int32_t reason, u_int32_t current_id) |
| { |
| struct ol_txrx_vdev_t *vdev = NULL, *tmp; |
| |
| TAILQ_FOREACH_SAFE(vdev, &pdev->vdev_list, vdev_list_elem, tmp) { |
| if (vdev->vdev_id != current_id) { |
| ol_txrx_vdev_unpause(vdev, reason); |
| } |
| } |
| } |
| #endif |
| |
| #ifdef QCA_BAD_PEER_TX_FLOW_CL |
| |
| /** |
| * ol_txrx_peer_bal_add_limit_peer() - add one peer into limit list |
| * @pdev: Pointer to PDEV structure. |
| * @peer_id: Peer Identifier. |
| * @peer_limit Peer limit threshold |
| * |
| * Add one peer into the limit list of pdev |
| * Note that the peer limit info will be also updated |
| * If it is the first time, start the timer |
| * |
| * Return: None |
| */ |
| void |
| ol_txrx_peer_bal_add_limit_peer(struct ol_txrx_pdev_t *pdev, |
| u_int16_t peer_id, u_int16_t peer_limit) |
| { |
| u_int16_t i, existed = 0; |
| struct ol_txrx_peer_t *peer = NULL; |
| |
| for (i = 0; i < pdev->tx_peer_bal.peer_num; i++){ |
| if (pdev->tx_peer_bal.limit_list[i].peer_id == peer_id) { |
| existed = 1; |
| break; |
| } |
| } |
| |
| if (!existed) { |
| u_int32_t peer_num = pdev->tx_peer_bal.peer_num; |
| /* Check if peer_num has reached the capabilit */ |
| if (peer_num >= MAX_NO_PEERS_IN_LIMIT) { |
| TX_SCHED_DEBUG_PRINT_ALWAYS( |
| "reach the maxinum peer num %d\n", |
| peer_num); |
| return; |
| } |
| |
| pdev->tx_peer_bal.limit_list[peer_num].peer_id = peer_id; |
| pdev->tx_peer_bal.limit_list[peer_num].limit_flag = TRUE; |
| pdev->tx_peer_bal.limit_list[peer_num].limit = peer_limit; |
| pdev->tx_peer_bal.peer_num++; |
| |
| peer = ol_txrx_peer_find_by_id(pdev, peer_id); |
| if (peer) { |
| peer->tx_limit_flag = TRUE; |
| peer->tx_limit = peer_limit; |
| } |
| |
| TX_SCHED_DEBUG_PRINT_ALWAYS( |
| "Add one peer into limit queue, peer_id %d, cur peer num %d\n", |
| peer_id, |
| pdev->tx_peer_bal.peer_num); |
| } |
| |
| /* Only start the timer once */ |
| if (pdev->tx_peer_bal.peer_bal_timer_state == |
| ol_tx_peer_bal_timer_inactive) { |
| adf_os_timer_start(&pdev->tx_peer_bal.peer_bal_timer, |
| pdev->tx_peer_bal.peer_bal_period_ms); |
| pdev->tx_peer_bal.peer_bal_timer_state = |
| ol_tx_peer_bal_timer_active; |
| } |
| } |
| |
| /** |
| * ol_txrx_peer_bal_remove_limit_peer() - remove one peer from limit list |
| * @pdev: Pointer to PDEV structure. |
| * @peer_id: Peer Identifier. |
| * |
| * Remove one peer from the limit list of pdev |
| * Note that Only stop the timer if no peer in limit state |
| * |
| * Return: NULL |
| */ |
| void |
| ol_txrx_peer_bal_remove_limit_peer(struct ol_txrx_pdev_t *pdev, |
| u_int16_t peer_id) |
| { |
| u_int16_t i; |
| struct ol_txrx_peer_t *peer = NULL; |
| |
| for (i = 0; i < pdev->tx_peer_bal.peer_num; i++) { |
| if ( pdev->tx_peer_bal.limit_list[i].peer_id == peer_id) { |
| pdev->tx_peer_bal.limit_list[i] = |
| pdev->tx_peer_bal.limit_list[pdev->tx_peer_bal.peer_num - 1]; |
| pdev->tx_peer_bal.peer_num--; |
| |
| peer = ol_txrx_peer_find_by_id(pdev, peer_id); |
| if (peer) { |
| peer->tx_limit_flag = FALSE; |
| } |
| |
| TX_SCHED_DEBUG_PRINT( |
| "Remove one peer from limitq, peer_id %d, cur peer num %d\n", |
| peer_id, |
| pdev->tx_peer_bal.peer_num); |
| break; |
| } |
| } |
| |
| /* Only stop the timer if no peer in limit state */ |
| if (pdev->tx_peer_bal.peer_num == 0) { |
| adf_os_timer_cancel(&pdev->tx_peer_bal.peer_bal_timer); |
| pdev->tx_peer_bal.peer_bal_timer_state = |
| ol_tx_peer_bal_timer_inactive; |
| } |
| } |
| |
| void |
| ol_txrx_peer_pause_but_no_mgmt_q(ol_txrx_peer_handle peer) |
| { |
| struct ol_txrx_pdev_t *pdev = peer->vdev->pdev; |
| |
| /* TO DO: log the queue pause */ |
| |
| /* acquire the mutex lock, since we'll be modifying the queues */ |
| TX_SCHED_DEBUG_PRINT("Enter %s\n", __func__); |
| adf_os_spin_lock_bh(&pdev->tx_queue_spinlock); |
| |
| ol_txrx_peer_pause_but_no_mgmt_q_base(pdev, peer); |
| |
| adf_os_spin_unlock_bh(&pdev->tx_queue_spinlock); |
| TX_SCHED_DEBUG_PRINT("Leave %s\n", __func__); |
| } |
| |
| void |
| ol_txrx_peer_unpause_but_no_mgmt_q(ol_txrx_peer_handle peer) |
| { |
| struct ol_txrx_pdev_t *pdev = peer->vdev->pdev; |
| |
| /* TO DO: log the queue pause */ |
| |
| /* acquire the mutex lock, since we'll be modifying the queues */ |
| TX_SCHED_DEBUG_PRINT("Enter %s\n", __func__); |
| adf_os_spin_lock_bh(&pdev->tx_queue_spinlock); |
| |
| ol_txrx_peer_unpause_but_no_mgmt_q_base(pdev, peer); |
| |
| adf_os_spin_unlock_bh(&pdev->tx_queue_spinlock); |
| TX_SCHED_DEBUG_PRINT("Leave %s\n", __func__); |
| } |
| |
| u_int16_t |
| ol_tx_bad_peer_dequeue_check(struct ol_tx_frms_queue_t *txq, |
| u_int16_t max_frames, |
| u_int16_t *tx_limit_flag) |
| { |
| if (txq && (txq->peer) && (txq->peer->tx_limit_flag) |
| && (txq->peer->tx_limit < max_frames)) { |
| TX_SCHED_DEBUG_PRINT("Peer ID %d goes to limit, threshold is %d\n", |
| txq->peer->peer_ids[0], txq->peer->tx_limit); |
| *tx_limit_flag = 1; |
| return txq->peer->tx_limit; |
| } else { |
| return max_frames; |
| } |
| } |
| |
| void |
| ol_tx_bad_peer_update_tx_limit(struct ol_txrx_pdev_t *pdev, |
| struct ol_tx_frms_queue_t *txq, |
| u_int16_t frames, |
| u_int16_t tx_limit_flag) |
| { |
| adf_os_spin_lock_bh(&pdev->tx_peer_bal.mutex); |
| if (txq && tx_limit_flag && (txq->peer) && (txq->peer->tx_limit_flag)) { |
| if (txq->peer->tx_limit < frames) { |
| txq->peer->tx_limit = 0; |
| } else { |
| txq->peer->tx_limit -= frames; |
| } |
| TX_SCHED_DEBUG_PRINT_ALWAYS("Peer ID %d in limit, deque %d frms\n", |
| txq->peer->peer_ids[0], frames); |
| } else if (txq->peer) { |
| TX_SCHED_DEBUG_PRINT("Download peer_id %d, num_frames %d\n", |
| txq->peer->peer_ids[0], frames); |
| } |
| adf_os_spin_unlock_bh(&pdev->tx_peer_bal.mutex); |
| } |
| |
| void |
| ol_txrx_bad_peer_txctl_set_setting(struct ol_txrx_pdev_t *pdev, |
| int enable, int period, int txq_limit) |
| { |
| if (enable) { |
| pdev->tx_peer_bal.enabled = ol_tx_peer_bal_enable; |
| } else { |
| pdev->tx_peer_bal.enabled = ol_tx_peer_bal_disable; |
| } |
| /* Set the current settingl */ |
| pdev->tx_peer_bal.peer_bal_period_ms = period; |
| pdev->tx_peer_bal.peer_bal_txq_limit = txq_limit; |
| } |
| |
| void |
| ol_txrx_bad_peer_txctl_update_threshold(struct ol_txrx_pdev_t *pdev, |
| int level, int tput_thresh, int tx_limit) |
| { |
| /* Set the current settingl */ |
| pdev->tx_peer_bal.ctl_thresh[level].tput_thresh = |
| tput_thresh; |
| pdev->tx_peer_bal.ctl_thresh[level].tx_limit = |
| tx_limit; |
| } |
| |
| void |
| ol_tx_pdev_peer_bal_timer(void *context) |
| { |
| int i; |
| struct ol_txrx_pdev_t *pdev = (struct ol_txrx_pdev_t *)context; |
| |
| adf_os_spin_lock_bh(&pdev->tx_peer_bal.mutex); |
| |
| for (i = 0; i < pdev->tx_peer_bal.peer_num; i++) { |
| if (pdev->tx_peer_bal.limit_list[i].limit_flag) { |
| u_int16_t peer_id = |
| pdev->tx_peer_bal.limit_list[i].peer_id; |
| u_int16_t tx_limit = |
| pdev->tx_peer_bal.limit_list[i].limit; |
| |
| struct ol_txrx_peer_t *peer = NULL; |
| peer = ol_txrx_peer_find_by_id(pdev, peer_id); |
| TX_SCHED_DEBUG_PRINT("%s peer_id %d peer = 0x%x tx limit %d\n", |
| __FUNCTION__, peer_id, |
| (int)peer, tx_limit); |
| |
| /* It is possible the peer limit is still not 0, |
| but it is the scenario should not be cared */ |
| if (peer) { |
| peer->tx_limit = tx_limit; |
| } else { |
| ol_txrx_peer_bal_remove_limit_peer(pdev, |
| peer_id); |
| TX_SCHED_DEBUG_PRINT_ALWAYS("No such a peer, peer id = %d\n", |
| peer_id); |
| } |
| } |
| } |
| |
| adf_os_spin_unlock_bh(&pdev->tx_peer_bal.mutex); |
| |
| if (pdev->tx_peer_bal.peer_num) { |
| ol_tx_sched(pdev); |
| adf_os_timer_start(&pdev->tx_peer_bal.peer_bal_timer, |
| pdev->tx_peer_bal.peer_bal_period_ms); |
| } |
| } |
| |
| void |
| ol_txrx_set_txq_peer( |
| struct ol_tx_frms_queue_t *txq, |
| struct ol_txrx_peer_t *peer) |
| { |
| if (txq) { |
| txq->peer = peer; |
| } |
| } |
| |
| void ol_tx_badpeer_flow_cl_init(struct ol_txrx_pdev_t *pdev) |
| { |
| u_int32_t timer_period; |
| |
| adf_os_spinlock_init(&pdev->tx_peer_bal.mutex); |
| pdev->tx_peer_bal.peer_num = 0; |
| pdev->tx_peer_bal.peer_bal_timer_state |
| = ol_tx_peer_bal_timer_inactive; |
| |
| timer_period = 2000; |
| pdev->tx_peer_bal.peer_bal_period_ms = timer_period; |
| |
| adf_os_timer_init( |
| pdev->osdev, |
| &pdev->tx_peer_bal.peer_bal_timer, |
| ol_tx_pdev_peer_bal_timer, |
| pdev); |
| } |
| |
| void ol_tx_badpeer_flow_cl_deinit(struct ol_txrx_pdev_t *pdev) |
| { |
| adf_os_timer_cancel(&pdev->tx_peer_bal.peer_bal_timer); |
| pdev->tx_peer_bal.peer_bal_timer_state = |
| ol_tx_peer_bal_timer_inactive; |
| adf_os_timer_free(&pdev->tx_peer_bal.peer_bal_timer); |
| adf_os_spinlock_destroy(&pdev->tx_peer_bal.mutex); |
| } |
| |
| void |
| ol_txrx_peer_link_status_handler( |
| ol_txrx_pdev_handle pdev, |
| u_int16_t peer_num, |
| struct rate_report_t* peer_link_status) |
| { |
| u_int16_t i = 0; |
| struct ol_txrx_peer_t *peer = NULL; |
| |
| if (NULL == pdev) { |
| TX_SCHED_DEBUG_PRINT_ALWAYS("Error: NULL pdev handler \n"); |
| return; |
| } |
| |
| if (NULL == peer_link_status) { |
| TX_SCHED_DEBUG_PRINT_ALWAYS( |
| "Error:NULL link report message. peer num %d\n", |
| peer_num); |
| return; |
| } |
| |
| /* Check if bad peer tx flow CL is enabled */ |
| if (pdev->tx_peer_bal.enabled != ol_tx_peer_bal_enable){ |
| TX_SCHED_DEBUG_PRINT_ALWAYS( |
| "Bad peer tx flow CL is not enabled, ignore it\n"); |
| return; |
| } |
| |
| /* Check peer_num is reasonable */ |
| if (peer_num > MAX_NO_PEERS_IN_LIMIT){ |
| TX_SCHED_DEBUG_PRINT_ALWAYS( |
| "%s: Bad peer_num %d \n", __func__, peer_num); |
| return; |
| } |
| |
| VOS_TRACE(VOS_MODULE_ID_TXRX, VOS_TRACE_LEVEL_DEBUG, |
| "%s: peer_num %d", __func__, peer_num); |
| |
| for (i = 0; i < peer_num; i++) { |
| u_int16_t peer_limit, peer_id; |
| u_int16_t pause_flag, unpause_flag; |
| u_int32_t peer_phy, peer_tput; |
| |
| peer_id = peer_link_status->id; |
| peer_phy = peer_link_status->phy; |
| peer_tput = peer_link_status->rate; |
| |
| TX_SCHED_DEBUG_PRINT("%s: peer id %d tput %d phy %d\n", |
| __func__, peer_id, peer_tput, peer_phy); |
| |
| /* Sanity check for the PHY mode value */ |
| if (peer_phy > TXRX_IEEE11_AC) { |
| TX_SCHED_DEBUG_PRINT_ALWAYS( |
| "%s: PHY value is illegal: %d, and the peer_id %d \n", |
| __func__, peer_link_status->phy, peer_id); |
| continue; |
| } |
| pause_flag = FALSE; |
| unpause_flag = FALSE; |
| peer_limit = 0; |
| |
| /* From now on, PHY, PER info should be all fine */ |
| adf_os_spin_lock_bh(&pdev->tx_peer_bal.mutex); |
| |
| /* Update link status analysis for each peer */ |
| peer = ol_txrx_peer_find_by_id(pdev, peer_id); |
| if (peer) { |
| u_int32_t thresh, limit, phy; |
| phy = peer_link_status->phy; |
| thresh = pdev->tx_peer_bal.ctl_thresh[phy].tput_thresh; |
| limit = pdev->tx_peer_bal.ctl_thresh[phy].tx_limit; |
| |
| if (((peer->tx_pause_flag) || (peer->tx_limit_flag)) |
| && (peer_tput) && (peer_tput < thresh)) { |
| peer_limit = limit; |
| } |
| |
| if (peer_limit) { |
| ol_txrx_peer_bal_add_limit_peer(pdev, peer_id, |
| peer_limit); |
| } else if (pdev->tx_peer_bal.peer_num) { |
| TX_SCHED_DEBUG_PRINT("%s: Check if peer_id %d exit limit\n", |
| __func__, peer_id); |
| ol_txrx_peer_bal_remove_limit_peer(pdev, peer_id); |
| } |
| if ((peer_tput == 0) && (peer->tx_pause_flag == FALSE)) { |
| peer->tx_pause_flag = TRUE; |
| pause_flag = TRUE; |
| } else if (peer->tx_pause_flag){ |
| unpause_flag = TRUE; |
| peer->tx_pause_flag = FALSE; |
| } |
| } else { |
| TX_SCHED_DEBUG_PRINT("%s: Remove peer_id %d from limit list\n", |
| __func__, peer_id); |
| ol_txrx_peer_bal_remove_limit_peer(pdev, peer_id); |
| } |
| |
| peer_link_status++; |
| adf_os_spin_unlock_bh(&pdev->tx_peer_bal.mutex); |
| if (pause_flag) { |
| ol_txrx_peer_pause_but_no_mgmt_q(peer); |
| } else if (unpause_flag) { |
| ol_txrx_peer_unpause_but_no_mgmt_q(peer); |
| } |
| } |
| } |
| #endif /* QCA_BAD_PEER_TX_FLOW_CL */ |
| |
| void |
| ol_txrx_vdev_pause(ol_txrx_vdev_handle vdev, u_int32_t reason) |
| { |
| /* TO DO: log the queue pause */ |
| /* acquire the mutex lock, since we'll be modifying the queues */ |
| TX_SCHED_DEBUG_PRINT("Enter %s\n", __func__); |
| |
| if (vdev->pdev->cfg.is_high_latency) { |
| #if defined(CONFIG_HL_SUPPORT) |
| struct ol_txrx_pdev_t *pdev = vdev->pdev; |
| struct ol_txrx_peer_t *peer; |
| /* use peer_ref_mutex before accessing peer_list */ |
| adf_os_spin_lock_bh(&pdev->peer_ref_mutex); |
| adf_os_spin_lock_bh(&pdev->tx_queue_spinlock); |
| if((vdev->hl_paused_reason & reason) == 0) { |
| vdev->hl_paused_reason |= reason; |
| TAILQ_FOREACH(peer, &vdev->peer_list, peer_list_elem) { |
| ol_txrx_peer_pause_base(pdev, peer); |
| } |
| } |
| adf_os_spin_unlock_bh(&pdev->tx_queue_spinlock); |
| adf_os_spin_unlock_bh(&pdev->peer_ref_mutex); |
| #endif /* defined(CONFIG_HL_SUPPORT) */ |
| } else { |
| adf_os_spin_lock_bh(&vdev->ll_pause.mutex); |
| vdev->ll_pause.paused_reason |= reason; |
| vdev->ll_pause.pause_timestamp = |
| adf_os_gettimestamp(); |
| vdev->ll_pause.q_pause_cnt++; |
| vdev->ll_pause.is_q_paused = TRUE; |
| adf_os_spin_unlock_bh(&vdev->ll_pause.mutex); |
| } |
| |
| TX_SCHED_DEBUG_PRINT("Leave %s\n", __func__); |
| } |
| |
| void |
| ol_txrx_vdev_unpause(ol_txrx_vdev_handle vdev, u_int32_t reason) |
| { |
| /* TO DO: log the queue unpause */ |
| /* acquire the mutex lock, since we'll be modifying the queues */ |
| TX_SCHED_DEBUG_PRINT("Enter %s\n", __func__); |
| |
| if (vdev->pdev->cfg.is_high_latency) { |
| #if defined(CONFIG_HL_SUPPORT) |
| struct ol_txrx_pdev_t *pdev = vdev->pdev; |
| struct ol_txrx_peer_t *peer; |
| |
| /* take peer_ref_mutex before accessing peer_list */ |
| adf_os_spin_lock_bh(&pdev->peer_ref_mutex); |
| adf_os_spin_lock_bh(&pdev->tx_queue_spinlock); |
| if (vdev->hl_paused_reason & reason) { |
| vdev->hl_paused_reason &= ~reason; |
| |
| TAILQ_FOREACH(peer, &vdev->peer_list, peer_list_elem) { |
| int i; |
| for (i = 0; i < ARRAY_LEN(peer->txqs); i++) { |
| ol_txrx_peer_tid_unpause_base(pdev, peer, i); |
| } |
| } |
| } |
| adf_os_spin_unlock_bh(&pdev->tx_queue_spinlock); |
| adf_os_spin_unlock_bh(&pdev->peer_ref_mutex); |
| #endif /* defined(CONFIG_HL_SUPPORT) */ |
| } else { |
| adf_os_spin_lock_bh(&vdev->ll_pause.mutex); |
| if (vdev->ll_pause.paused_reason & reason) |
| { |
| vdev->ll_pause.paused_reason &= ~reason; |
| vdev->ll_pause.is_q_paused = FALSE; |
| vdev->ll_pause.q_unpause_cnt++; |
| #ifdef QCA_SUPPORT_TXRX_VDEV_LL_TXQ |
| if (reason == OL_TXQ_PAUSE_REASON_VDEV_SUSPEND) { |
| adf_os_spin_unlock_bh(&vdev->ll_pause.mutex); |
| ol_tx_vdev_ll_pause_start_timer(vdev); |
| } |
| else |
| #endif |
| if (!vdev->ll_pause.paused_reason) { |
| adf_os_spin_unlock_bh(&vdev->ll_pause.mutex); |
| ol_tx_vdev_ll_pause_queue_send(vdev); |
| } else { |
| adf_os_spin_unlock_bh(&vdev->ll_pause.mutex); |
| } |
| } else { |
| adf_os_spin_unlock_bh(&vdev->ll_pause.mutex); |
| } |
| } |
| |
| TX_SCHED_DEBUG_PRINT("Leave %s\n", __func__); |
| } |
| |
| void |
| ol_txrx_vdev_flush(ol_txrx_vdev_handle vdev) |
| { |
| if (vdev->pdev->cfg.is_high_latency) { |
| #if defined(CONFIG_HL_SUPPORT) |
| ol_tx_queue_vdev_flush(vdev->pdev, vdev); |
| #endif |
| } else { |
| adf_os_spin_lock_bh(&vdev->ll_pause.mutex); |
| adf_os_timer_cancel(&vdev->ll_pause.timer); |
| vdev->ll_pause.is_q_timer_on = FALSE; |
| while (vdev->ll_pause.txq.head) { |
| adf_nbuf_t next = adf_nbuf_next(vdev->ll_pause.txq.head); |
| adf_nbuf_set_next(vdev->ll_pause.txq.head, NULL); |
| adf_nbuf_unmap(vdev->pdev->osdev, vdev->ll_pause.txq.head, |
| ADF_OS_DMA_TO_DEVICE); |
| adf_nbuf_tx_free(vdev->ll_pause.txq.head, ADF_NBUF_PKT_ERROR); |
| vdev->ll_pause.txq.head = next; |
| } |
| vdev->ll_pause.txq.tail = NULL; |
| vdev->ll_pause.txq.depth = 0; |
| adf_os_spin_unlock_bh(&vdev->ll_pause.mutex); |
| } |
| } |
| |
| #endif // defined(CONFIG_HL_SUPPORT) || defined(QCA_SUPPORT_TXRX_VDEV_PAUSE_LL) |
| |
| /*--- LL tx throttle queue code --------------------------------------------*/ |
| #if defined(QCA_SUPPORT_TX_THROTTLE) |
| u_int8_t ol_tx_pdev_is_target_empty(void) |
| { |
| /* TM TODO */ |
| return 1; |
| } |
| |
| void ol_tx_pdev_throttle_phase_timer(void *context) |
| { |
| struct ol_txrx_pdev_t *pdev = (struct ol_txrx_pdev_t *)context; |
| int ms = 0; |
| throttle_level cur_level; |
| throttle_phase cur_phase; |
| |
| /* update the phase */ |
| pdev->tx_throttle.current_throttle_phase++; |
| |
| if (pdev->tx_throttle.current_throttle_phase == THROTTLE_PHASE_MAX) { |
| pdev->tx_throttle.current_throttle_phase = THROTTLE_PHASE_OFF; |
| } |
| |
| if (pdev->tx_throttle.current_throttle_phase == THROTTLE_PHASE_OFF) { |
| if (ol_tx_pdev_is_target_empty(/*pdev*/)) { |
| TXRX_PRINT(TXRX_PRINT_LEVEL_WARN, "throttle phase --> OFF\n"); |
| |
| if (pdev->cfg.is_high_latency) |
| ol_txrx_throttle_pause(pdev); |
| |
| cur_level = pdev->tx_throttle.current_throttle_level; |
| cur_phase = pdev->tx_throttle.current_throttle_phase; |
| ms = pdev->tx_throttle.throttle_time_ms[cur_level][cur_phase]; |
| if (pdev->tx_throttle.current_throttle_level != |
| THROTTLE_LEVEL_0) { |
| TXRX_PRINT(TXRX_PRINT_LEVEL_WARN, "start timer %d ms\n", ms); |
| adf_os_timer_mod(&pdev->tx_throttle.phase_timer, ms); |
| } |
| } |
| } |
| else /* THROTTLE_PHASE_ON */ |
| { |
| TXRX_PRINT(TXRX_PRINT_LEVEL_WARN, "throttle phase --> ON\n"); |
| |
| if (pdev->cfg.is_high_latency) |
| ol_txrx_throttle_unpause(pdev); |
| #ifdef QCA_SUPPORT_TXRX_VDEV_LL_TXQ |
| else |
| ol_tx_pdev_ll_pause_queue_send_all(pdev); |
| #endif |
| |
| cur_level = pdev->tx_throttle.current_throttle_level; |
| cur_phase = pdev->tx_throttle.current_throttle_phase; |
| ms = pdev->tx_throttle.throttle_time_ms[cur_level][cur_phase]; |
| if (pdev->tx_throttle.current_throttle_level != THROTTLE_LEVEL_0) { |
| TXRX_PRINT(TXRX_PRINT_LEVEL_WARN, "start timer %d ms\n", ms); |
| adf_os_timer_mod(&pdev->tx_throttle.phase_timer, ms); |
| } |
| } |
| } |
| |
| #ifdef QCA_SUPPORT_TXRX_VDEV_LL_TXQ |
| void ol_tx_pdev_throttle_tx_timer(void *context) |
| { |
| struct ol_txrx_pdev_t *pdev = (struct ol_txrx_pdev_t *)context; |
| ol_tx_pdev_ll_pause_queue_send_all(pdev); |
| } |
| #endif |
| |
| void ol_tx_throttle_set_level(struct ol_txrx_pdev_t *pdev, int level) |
| { |
| int ms = 0; |
| |
| if (level >= THROTTLE_LEVEL_MAX) { |
| TXRX_PRINT(TXRX_PRINT_LEVEL_WARN, |
| "%s invalid throttle level set %d, ignoring\n", |
| __func__, level); |
| return; |
| } |
| |
| TXRX_PRINT(TXRX_PRINT_LEVEL_ERR, "Setting throttle level %d\n", level); |
| |
| /* Set the current throttle level */ |
| pdev->tx_throttle.current_throttle_level = (throttle_level)level; |
| |
| if (pdev->cfg.is_high_latency) { |
| |
| adf_os_timer_cancel(&pdev->tx_throttle.phase_timer); |
| |
| /* Set the phase */ |
| if (level != THROTTLE_LEVEL_0) { |
| pdev->tx_throttle.current_throttle_phase = THROTTLE_PHASE_OFF; |
| ms = pdev->tx_throttle.throttle_time_ms[level][THROTTLE_PHASE_OFF]; |
| /* pause all */ |
| ol_txrx_throttle_pause(pdev); |
| adf_os_timer_mod(&pdev->tx_throttle.phase_timer, ms); |
| } else { |
| pdev->tx_throttle.current_throttle_phase = THROTTLE_PHASE_ON; |
| ms = pdev->tx_throttle.throttle_time_ms[level][THROTTLE_PHASE_ON]; |
| /* unpause all */ |
| ol_txrx_throttle_unpause(pdev); |
| } |
| } else { |
| /* Reset the phase */ |
| pdev->tx_throttle.current_throttle_phase = THROTTLE_PHASE_OFF; |
| |
| /* Start with the new time */ |
| ms = pdev->tx_throttle.throttle_time_ms[level][THROTTLE_PHASE_OFF]; |
| |
| adf_os_timer_cancel(&pdev->tx_throttle.phase_timer); |
| adf_os_timer_mod(&pdev->tx_throttle.phase_timer, ms); |
| } |
| } |
| |
| void ol_tx_throttle_init_period(struct ol_txrx_pdev_t *pdev, int period, |
| u_int8_t *dutycycle_level) |
| { |
| int i; |
| |
| /* Set the current throttle level */ |
| pdev->tx_throttle.throttle_period_ms = period; |
| |
| TXRX_PRINT(TXRX_PRINT_LEVEL_WARN, "level OFF ON\n"); |
| for (i = 0; i < THROTTLE_LEVEL_MAX; i++) { |
| pdev->tx_throttle.throttle_time_ms[i][THROTTLE_PHASE_ON] = |
| pdev->tx_throttle.throttle_period_ms - |
| ((dutycycle_level[i] * pdev->tx_throttle.throttle_period_ms) |
| /100); |
| pdev->tx_throttle.throttle_time_ms[i][THROTTLE_PHASE_OFF] = |
| pdev->tx_throttle.throttle_period_ms - |
| pdev->tx_throttle.throttle_time_ms[i][THROTTLE_PHASE_ON]; |
| TXRX_PRINT(TXRX_PRINT_LEVEL_WARN, "%d %d %d\n", i, |
| pdev->tx_throttle.throttle_time_ms[i][THROTTLE_PHASE_OFF], |
| pdev->tx_throttle.throttle_time_ms[i][THROTTLE_PHASE_ON]); |
| } |
| } |
| |
| void ol_tx_throttle_init(struct ol_txrx_pdev_t *pdev) |
| { |
| u_int32_t throttle_period; |
| u_int8_t dutycycle_level[THROTTLE_LEVEL_MAX]; |
| int i; |
| |
| pdev->tx_throttle.current_throttle_level = THROTTLE_LEVEL_0; |
| pdev->tx_throttle.current_throttle_phase = THROTTLE_PHASE_OFF; |
| adf_os_spinlock_init(&pdev->tx_throttle.mutex); |
| |
| throttle_period = ol_cfg_throttle_period_ms(pdev->ctrl_pdev); |
| |
| for (i = 0; i < THROTTLE_LEVEL_MAX; i++) |
| dutycycle_level[i] = ol_cfg_throttle_duty_cycle_level(pdev->ctrl_pdev, |
| i); |
| |
| ol_tx_throttle_init_period(pdev, throttle_period, &dutycycle_level[0]); |
| |
| adf_os_timer_init( |
| pdev->osdev, |
| &pdev->tx_throttle.phase_timer, |
| ol_tx_pdev_throttle_phase_timer, |
| pdev, ADF_DEFERRABLE_TIMER); |
| |
| #ifdef QCA_SUPPORT_TXRX_VDEV_LL_TXQ |
| adf_os_timer_init( |
| pdev->osdev, |
| &pdev->tx_throttle.tx_timer, |
| ol_tx_pdev_throttle_tx_timer, |
| pdev, ADF_DEFERRABLE_TIMER); |
| #endif |
| |
| pdev->tx_throttle.tx_threshold = THROTTLE_TX_THRESHOLD; |
| } |
| #endif /* QCA_SUPPORT_TX_THROTTLE */ |
| /*--- End of LL tx throttle queue code ---------------------------------------*/ |
| |
| #if defined(CONFIG_HL_SUPPORT) |
| |
| /*--- ADDBA triggering functions --------------------------------------------*/ |
| |
| |
| /*=== debug functions =======================================================*/ |
| |
| /*--- queue event log -------------------------------------------------------*/ |
| |
| #if defined(DEBUG_HL_LOGGING) |
| |
| static void |
| ol_tx_queue_log_entry_type_info( |
| u_int8_t *type, int *size, int *align, int var_size) |
| { |
| switch (*type) { |
| case ol_tx_log_entry_type_enqueue: |
| case ol_tx_log_entry_type_dequeue: |
| case ol_tx_log_entry_type_queue_free: |
| *size = sizeof(struct ol_tx_log_queue_add_t); |
| *align = 2; |
| break; |
| |
| case ol_tx_log_entry_type_queue_state: |
| *size = offsetof(struct ol_tx_log_queue_state_var_sz_t, data); |
| *align = 4; |
| if (var_size) { |
| /* read the variable-sized record, to see how large it is */ |
| int align_pad; |
| struct ol_tx_log_queue_state_var_sz_t *record; |
| |
| align_pad = |
| (*align - (uint32_t)(((unsigned long) type) + 1)) & (*align - 1); |
| record = (struct ol_tx_log_queue_state_var_sz_t *) |
| (type + 1 + align_pad); |
| *size += record->num_cats_active * |
| (sizeof(u_int32_t) /* bytes */ + sizeof(u_int16_t) /* frms */); |
| } |
| break; |
| |
| //case ol_tx_log_entry_type_drop: |
| default: |
| *size = 0; |
| *align = 0; |
| }; |
| } |
| |
| static void |
| ol_tx_queue_log_oldest_update(struct ol_txrx_pdev_t *pdev, int offset) |
| { |
| int oldest_record_offset; |
| |
| /* |
| * If the offset of the oldest record is between the current and |
| * new values of the offset of the newest record, then the oldest |
| * record has to be dropped from the log to provide room for the |
| * newest record. |
| * Advance the offset of the oldest record until it points to a |
| * record that is beyond the new value of the offset of the newest |
| * record. |
| */ |
| if (!pdev->txq_log.wrapped) { |
| /* |
| * The log has not even filled up yet - no need to remove |
| * the oldest record to make room for a new record. |
| */ |
| return; |
| } |
| |
| if (offset > pdev->txq_log.offset) { |
| /* |
| * not wraparound - |
| * The oldest record offset may have already wrapped around, |
| * even if the newest record has not. In this case, then |
| * the oldest record offset is fine where it is. |
| */ |
| if (pdev->txq_log.oldest_record_offset == 0) { |
| return; |
| } |
| oldest_record_offset = pdev->txq_log.oldest_record_offset; |
| } else { |
| /* wraparound */ |
| oldest_record_offset = 0; |
| } |
| |
| while (oldest_record_offset < offset) { |
| int size, align, align_pad; |
| u_int8_t type; |
| |
| type = pdev->txq_log.data[oldest_record_offset]; |
| if (type == ol_tx_log_entry_type_wrap) { |
| oldest_record_offset = 0; |
| break; |
| } |
| ol_tx_queue_log_entry_type_info( |
| &pdev->txq_log.data[oldest_record_offset], &size, &align, 1); |
| align_pad = |
| (align - ((oldest_record_offset + 1/*type*/))) & (align - 1); |
| /* |
| VOS_TRACE(VOS_MODULE_ID_TXRX, VOS_TRACE_LEVEL_INFO_LOW, |
| "TXQ LOG old alloc: offset %d, type %d, size %d (%d)\n", |
| oldest_record_offset, type, size, size + 1 + align_pad); |
| */ |
| oldest_record_offset += size + 1 + align_pad; |
| } |
| if (oldest_record_offset >= pdev->txq_log.size) { |
| oldest_record_offset = 0; |
| } |
| pdev->txq_log.oldest_record_offset = oldest_record_offset; |
| } |
| |
| void* |
| ol_tx_queue_log_alloc( |
| struct ol_txrx_pdev_t *pdev, |
| u_int8_t type /* ol_tx_log_entry_type */, |
| int extra_bytes) |
| { |
| int size, align, align_pad; |
| int offset; |
| |
| ol_tx_queue_log_entry_type_info(&type, &size, &align, 0); |
| size += extra_bytes; |
| |
| offset = pdev->txq_log.offset; |
| align_pad = (align - ((offset + 1/*type*/))) & (align - 1); |
| |
| if (pdev->txq_log.size - offset >= size + 1 + align_pad) { |
| /* no need to wrap around */ |
| goto alloc_found; |
| } |
| if (! pdev->txq_log.allow_wrap) { |
| return NULL; /* log is full and can't wrap */ |
| } |
| /* handle wrap-around */ |
| pdev->txq_log.wrapped = 1; |
| offset = 0; |
| align_pad = (align - ((offset + 1/*type*/))) & (align - 1); |
| /* sanity check that the log is large enough to hold this entry */ |
| if (pdev->txq_log.size <= size + 1 + align_pad) { |
| return NULL; |
| } |
| |
| alloc_found: |
| ol_tx_queue_log_oldest_update(pdev, offset + size + 1 + align_pad); |
| if (offset == 0) { |
| pdev->txq_log.data[pdev->txq_log.offset] = ol_tx_log_entry_type_wrap; |
| } |
| /* |
| VOS_TRACE(VOS_MODULE_ID_TXRX, VOS_TRACE_LEVEL_INFO_LOW, |
| "TXQ LOG new alloc: offset %d, type %d, size %d (%d)\n", |
| offset, type, size, size + 1 + align_pad); |
| */ |
| pdev->txq_log.data[offset] = type; |
| pdev->txq_log.offset = offset + size + 1 + align_pad; |
| if (pdev->txq_log.offset >= pdev->txq_log.size) { |
| pdev->txq_log.offset = 0; |
| pdev->txq_log.wrapped = 1; |
| } |
| return &pdev->txq_log.data[offset + 1 + align_pad]; |
| } |
| |
| static int |
| ol_tx_queue_log_record_display(struct ol_txrx_pdev_t *pdev, int offset) |
| { |
| int size, align, align_pad; |
| u_int8_t type; |
| struct ol_txrx_peer_t *peer; |
| |
| adf_os_spin_lock_bh(&pdev->txq_log_spinlock); |
| type = pdev->txq_log.data[offset]; |
| ol_tx_queue_log_entry_type_info( |
| &pdev->txq_log.data[offset], &size, &align, 1); |
| align_pad = (align - ((offset + 1/*type*/))) & (align - 1); |
| |
| switch (type) { |
| case ol_tx_log_entry_type_enqueue: |
| { |
| struct ol_tx_log_queue_add_t record; |
| adf_os_mem_copy(&record, |
| &pdev->txq_log.data[offset + 1 + align_pad], |
| sizeof(struct ol_tx_log_queue_add_t)); |
| adf_os_spin_unlock_bh(&pdev->txq_log_spinlock); |
| |
| if (record.peer_id != 0xffff) { |
| peer = ol_txrx_peer_find_by_id(pdev, record.peer_id); |
| if (peer != NULL) |
| VOS_TRACE(VOS_MODULE_ID_TXRX, VOS_TRACE_LEVEL_ERROR, |
| " Q: %6d %5d %3d %4d (%02x:%02x:%02x:%02x:%02x:%02x)", |
| record.num_frms, record.num_bytes, record.tid, |
| record.peer_id, |
| 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]); |
| else |
| VOS_TRACE(VOS_MODULE_ID_TXRX, VOS_TRACE_LEVEL_ERROR, |
| " Q: %6d %5d %3d %4d", |
| record.num_frms, record.num_bytes, |
| record.tid, record.peer_id); |
| } else { |
| VOS_TRACE(VOS_MODULE_ID_TXRX, VOS_TRACE_LEVEL_INFO, |
| " Q: %6d %5d %3d from vdev", |
| record.num_frms, record.num_bytes, record.tid); |
| } |
| break; |
| } |
| case ol_tx_log_entry_type_dequeue: |
| { |
| struct ol_tx_log_queue_add_t record; |
| adf_os_mem_copy(&record, |
| &pdev->txq_log.data[offset + 1 + align_pad], |
| sizeof(struct ol_tx_log_queue_add_t)); |
| adf_os_spin_unlock_bh(&pdev->txq_log_spinlock); |
| |
| if (record.peer_id != 0xffff) { |
| peer = ol_txrx_peer_find_by_id(pdev, record.peer_id); |
| if (peer != NULL) |
| VOS_TRACE(VOS_MODULE_ID_TXRX, VOS_TRACE_LEVEL_ERROR, |
| " DQ: %6d %5d %3d %4d (%02x:%02x:%02x:%02x:%02x:%02x)", |
| record.num_frms, record.num_bytes, record.tid, |
| record.peer_id, |
| 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]); |
| else |
| VOS_TRACE(VOS_MODULE_ID_TXRX, VOS_TRACE_LEVEL_ERROR, |
| " DQ: %6d %5d %3d %4d", |
| record.num_frms, record.num_bytes, |
| record.tid, record.peer_id); |
| } else { |
| VOS_TRACE(VOS_MODULE_ID_TXRX, VOS_TRACE_LEVEL_INFO, |
| " DQ: %6d %5d %3d from vdev", |
| record.num_frms, record.num_bytes, record.tid); |
| } |
| break; |
| } |
| case ol_tx_log_entry_type_queue_free: |
| { |
| struct ol_tx_log_queue_add_t record; |
| adf_os_mem_copy(&record, |
| &pdev->txq_log.data[offset + 1 + align_pad], |
| sizeof(struct ol_tx_log_queue_add_t)); |
| adf_os_spin_unlock_bh(&pdev->txq_log_spinlock); |
| |
| if (record.peer_id != 0xffff) { |
| peer = ol_txrx_peer_find_by_id(pdev, record.peer_id); |
| if (peer != NULL) |
| VOS_TRACE(VOS_MODULE_ID_TXRX, VOS_TRACE_LEVEL_ERROR, |
| " F: %6d %5d %3d %4d (%02x:%02x:%02x:%02x:%02x:%02x)", |
| record.num_frms, record.num_bytes, record.tid, |
| record.peer_id, |
| 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]); |
| else |
| VOS_TRACE(VOS_MODULE_ID_TXRX, VOS_TRACE_LEVEL_ERROR, |
| " F: %6d %5d %3d %4d", |
| record.num_frms, record.num_bytes, |
| record.tid, record.peer_id); |
| } else { |
| /* shouldn't happen */ |
| VOS_TRACE(VOS_MODULE_ID_TXRX, VOS_TRACE_LEVEL_INFO, |
| "Unexpected vdev queue removal\n"); |
| } |
| break; |
| } |
| |
| case ol_tx_log_entry_type_queue_state: |
| { |
| int i, j; |
| u_int32_t active_bitmap; |
| struct ol_tx_log_queue_state_var_sz_t record; |
| u_int8_t *data; |
| |
| adf_os_mem_copy(&record, |
| &pdev->txq_log.data[offset + 1 + align_pad], |
| sizeof(struct ol_tx_log_queue_state_var_sz_t)); |
| adf_os_spin_unlock_bh(&pdev->txq_log_spinlock); |
| |
| VOS_TRACE(VOS_MODULE_ID_TXRX, VOS_TRACE_LEVEL_ERROR, |
| " S: bitmap = %#x", |
| record.active_bitmap); |
| data = &record.data[0]; |
| j = 0; |
| i = 0; |
| active_bitmap = record.active_bitmap; |
| while (active_bitmap) { |
| if (active_bitmap & 0x1) { |
| u_int16_t frms; |
| u_int32_t bytes; |
| |
| frms = data[0] | (data[1] << 8); |
| bytes = (data[2] << 0) | (data[3] << 8) | |
| (data[4] << 16) | (data[5] << 24); |
| VOS_TRACE(VOS_MODULE_ID_TXRX, VOS_TRACE_LEVEL_ERROR, |
| " cat %2d: %6d %5d", |
| i, frms, bytes); |
| data += 6; |
| j++; |
| } |
| i++; |
| active_bitmap >>= 1; |
| } |
| break; |
| } |
| |
| //case ol_tx_log_entry_type_drop: |
| |
| case ol_tx_log_entry_type_wrap: |
| adf_os_spin_unlock_bh(&pdev->txq_log_spinlock); |
| return -1 * offset; /* go back to the top */ |
| |
| default: |
| adf_os_spin_unlock_bh(&pdev->txq_log_spinlock); |
| VOS_TRACE(VOS_MODULE_ID_TXRX, VOS_TRACE_LEVEL_ERROR, |
| " *** invalid tx log entry type (%d)\n", type); |
| return 0; /* error */ |
| }; |
| |
| return size + 1 + align_pad; |
| } |
| |
| void |
| ol_tx_queue_log_display(struct ol_txrx_pdev_t *pdev) |
| { |
| int offset; |
| int unwrap; |
| |
| adf_os_spin_lock_bh(&pdev->txq_log_spinlock); |
| offset = pdev->txq_log.oldest_record_offset; |
| unwrap = pdev->txq_log.wrapped; |
| adf_os_spin_unlock_bh(&pdev->txq_log_spinlock); |
| /* |
| * In theory, this should use mutex to guard against the offset |
| * being changed while in use, but since this is just for debugging, |
| * don't bother. |
| */ |
| VOS_TRACE(VOS_MODULE_ID_TXRX, VOS_TRACE_LEVEL_ERROR, |
| "Current target credit: %d", |
| adf_os_atomic_read(&pdev->target_tx_credit)); |
| VOS_TRACE(VOS_MODULE_ID_TXRX, VOS_TRACE_LEVEL_ERROR, |
| "Tx queue log:"); |
| VOS_TRACE(VOS_MODULE_ID_TXRX, VOS_TRACE_LEVEL_ERROR, |
| " : Frames Bytes TID PEER"); |
| |
| while (unwrap || offset != pdev->txq_log.offset) { |
| int delta = ol_tx_queue_log_record_display(pdev, offset); |
| if (delta == 0) { |
| return; /* error */ |
| } |
| if (delta < 0) { |
| unwrap = 0; |
| } |
| offset += delta; |
| } |
| } |
| |
| void |
| ol_tx_queue_log_enqueue( |
| struct ol_txrx_pdev_t *pdev, |
| struct ol_txrx_msdu_info_t *msdu_info, |
| int frms, int bytes) |
| { |
| int tid; |
| u_int16_t peer_id = msdu_info->htt.info.peer_id; |
| struct ol_tx_log_queue_add_t *log_elem; |
| tid = msdu_info->htt.info.ext_tid; |
| |
| adf_os_spin_lock_bh(&pdev->txq_log_spinlock); |
| log_elem = ol_tx_queue_log_alloc(pdev, ol_tx_log_entry_type_enqueue, 0); |
| if (!log_elem) { |
| adf_os_spin_unlock_bh(&pdev->txq_log_spinlock); |
| return; |
| } |
| |
| log_elem->num_frms = frms; |
| log_elem->num_bytes = bytes; |
| log_elem->peer_id = peer_id; |
| log_elem->tid = tid; |
| adf_os_spin_unlock_bh(&pdev->txq_log_spinlock); |
| } |
| |
| void |
| ol_tx_queue_log_dequeue( |
| struct ol_txrx_pdev_t *pdev, |
| struct ol_tx_frms_queue_t *txq, |
| int frms, int bytes) |
| { |
| int ext_tid; |
| u_int16_t peer_id; |
| struct ol_tx_log_queue_add_t *log_elem; |
| |
| ext_tid = txq->ext_tid; |
| adf_os_spin_lock_bh(&pdev->txq_log_spinlock); |
| log_elem = ol_tx_queue_log_alloc(pdev, ol_tx_log_entry_type_dequeue, 0); |
| if (!log_elem) { |
| adf_os_spin_unlock_bh(&pdev->txq_log_spinlock); |
| return; |
| } |
| |
| if (ext_tid < OL_TX_NUM_TIDS) { |
| struct ol_txrx_peer_t *peer; |
| struct ol_tx_frms_queue_t *txq_base; |
| |
| txq_base = txq - ext_tid; |
| peer = container_of(txq_base, struct ol_txrx_peer_t, txqs[0]); |
| peer_id = peer->peer_ids[0]; |
| } else { |
| peer_id = ~0; |
| } |
| |
| log_elem->num_frms = frms; |
| log_elem->num_bytes = bytes; |
| log_elem->peer_id = peer_id; |
| log_elem->tid = ext_tid; |
| adf_os_spin_unlock_bh(&pdev->txq_log_spinlock); |
| } |
| |
| void |
| ol_tx_queue_log_free( |
| struct ol_txrx_pdev_t *pdev, |
| struct ol_tx_frms_queue_t *txq, |
| int tid, int frms, int bytes, bool is_peer_txq) |
| { |
| u_int16_t peer_id; |
| struct ol_tx_log_queue_add_t *log_elem; |
| |
| adf_os_spin_lock_bh(&pdev->txq_log_spinlock); |
| log_elem = ol_tx_queue_log_alloc(pdev, ol_tx_log_entry_type_queue_free, 0); |
| if (!log_elem) { |
| adf_os_spin_unlock_bh(&pdev->txq_log_spinlock); |
| return; |
| } |
| |
| if ((tid < OL_TX_NUM_TIDS) && is_peer_txq) { |
| struct ol_txrx_peer_t *peer; |
| struct ol_tx_frms_queue_t *txq_base; |
| |
| txq_base = txq - tid; |
| peer = container_of(txq_base, struct ol_txrx_peer_t, txqs[0]); |
| peer_id = peer->peer_ids[0]; |
| } else { |
| peer_id = ~0; |
| } |
| |
| log_elem->num_frms = frms; |
| log_elem->num_bytes = bytes; |
| log_elem->peer_id = peer_id; |
| log_elem->tid = tid; |
| adf_os_spin_unlock_bh(&pdev->txq_log_spinlock); |
| } |
| |
| void |
| ol_tx_queue_log_sched( |
| struct ol_txrx_pdev_t *pdev, |
| int credit, |
| int *num_cats, |
| u_int32_t **active_bitmap, |
| u_int8_t **data) |
| { |
| int data_size; |
| struct ol_tx_log_queue_state_var_sz_t *log_elem; |
| |
| data_size = sizeof(u_int32_t) /* bytes */ + sizeof(u_int16_t) /* frms */; |
| data_size *= *num_cats; |
| |
| adf_os_spin_lock_bh(&pdev->txq_log_spinlock); |
| log_elem = ol_tx_queue_log_alloc( |
| pdev, ol_tx_log_entry_type_queue_state, data_size); |
| if (!log_elem) { |
| *num_cats = 0; |
| adf_os_spin_unlock_bh(&pdev->txq_log_spinlock); |
| return; |
| } |
| log_elem->num_cats_active = *num_cats; |
| log_elem->active_bitmap = 0; |
| log_elem->credit = credit; |
| |
| *active_bitmap = &log_elem->active_bitmap; |
| *data = &log_elem->data[0]; |
| adf_os_spin_unlock_bh(&pdev->txq_log_spinlock); |
| } |
| |
| void |
| ol_tx_queue_log_clear(struct ol_txrx_pdev_t *pdev) |
| { |
| adf_os_spin_lock_bh(&pdev->txq_log_spinlock); |
| adf_os_mem_zero(&pdev->txq_log, sizeof(pdev->txq_log)); |
| pdev->txq_log.size = OL_TXQ_LOG_SIZE; |
| pdev->txq_log.oldest_record_offset = 0; |
| pdev->txq_log.offset = 0; |
| pdev->txq_log.allow_wrap = 1; |
| pdev->txq_log.wrapped = 0; |
| adf_os_spin_unlock_bh(&pdev->txq_log_spinlock); |
| } |
| #endif /* defined(DEBUG_HL_LOGGING) */ |
| |
| /*--- queue state printouts -------------------------------------------------*/ |
| |
| #if TXRX_DEBUG_LEVEL > 5 |
| |
| void |
| ol_tx_queue_display(struct ol_tx_frms_queue_t *txq, int indent) |
| { |
| char *state; |
| |
| state = (txq->flag == ol_tx_queue_active) ? "active" : "paused"; |
| VOS_TRACE(VOS_MODULE_ID_TXRX, VOS_TRACE_LEVEL_INFO_LOW, |
| "%*stxq %pK (%s): %d frms, %d bytes\n", |
| indent, " ", txq, state, txq->frms, txq->bytes); |
| } |
| |
| void |
| ol_tx_queues_display(struct ol_txrx_pdev_t *pdev) |
| { |
| struct ol_txrx_vdev_t *vdev; |
| |
| VOS_TRACE(VOS_MODULE_ID_TXRX, VOS_TRACE_LEVEL_INFO_LOW, |
| "pdev %pK tx queues:\n", pdev); |
| TAILQ_FOREACH(vdev, &pdev->vdev_list, vdev_list_elem) { |
| struct ol_txrx_peer_t *peer; |
| int i; |
| for (i = 0; i < ARRAY_LEN(vdev->txqs); i++) { |
| if (vdev->txqs[i].frms == 0) { |
| continue; |
| } |
| VOS_TRACE(VOS_MODULE_ID_TXRX, VOS_TRACE_LEVEL_INFO_LOW, |
| " vdev %d (%pK), txq %d\n", vdev->vdev_id, vdev, i); |
| ol_tx_queue_display(&vdev->txqs[i], 4); |
| } |
| TAILQ_FOREACH(peer, &vdev->peer_list, peer_list_elem) { |
| for (i = 0; i < ARRAY_LEN(peer->txqs); i++) { |
| if (peer->txqs[i].frms == 0) { |
| continue; |
| } |
| VOS_TRACE(VOS_MODULE_ID_TXRX, VOS_TRACE_LEVEL_INFO_LOW, |
| " peer %d (%pK), txq %d\n", |
| peer->peer_ids[0], vdev, i); |
| ol_tx_queue_display(&peer->txqs[i], 6); |
| } |
| } |
| } |
| } |
| |
| #endif |
| |
| #endif /* defined(CONFIG_HL_SUPPORT) */ |
| |
| #ifdef FEATURE_HL_GROUP_CREDIT_FLOW_CONTROL |
| static a_bool_t |
| ol_tx_vdev_has_tx_queue_group( |
| struct ol_tx_queue_group_t* group, |
| u_int8_t vdev_id) |
| { |
| u_int16_t vdev_bitmap; |
| vdev_bitmap = OL_TXQ_GROUP_VDEV_ID_MASK_GET(group->membership); |
| if (OL_TXQ_GROUP_VDEV_ID_BIT_MASK_GET(vdev_bitmap, vdev_id)) { |
| return A_TRUE; |
| } |
| return A_FALSE; |
| } |
| |
| static a_bool_t |
| ol_tx_ac_has_tx_queue_group( |
| struct ol_tx_queue_group_t* group, |
| u_int8_t ac) |
| { |
| u_int16_t ac_bitmap; |
| ac_bitmap = OL_TXQ_GROUP_AC_MASK_GET(group->membership); |
| if (OL_TXQ_GROUP_AC_BIT_MASK_GET(ac_bitmap, ac)) { |
| return A_TRUE; |
| } |
| return A_FALSE; |
| } |
| |
| u_int32_t ol_tx_txq_group_credit_limit( |
| struct ol_txrx_pdev_t *pdev, |
| struct ol_tx_frms_queue_t *txq, |
| u_int32_t credit) |
| { |
| u_int8_t i; |
| int updated_credit = credit; |
| /* |
| * If this tx queue belongs to a group, check whether the group's |
| * credit limit is more stringent than the global credit limit. |
| */ |
| for (i = 0; i < OL_TX_MAX_GROUPS_PER_QUEUE; i++) { |
| if (txq->group_ptrs[i]) { |
| int group_credit; |
| group_credit = adf_os_atomic_read(&txq->group_ptrs[i]->credit); |
| updated_credit = MIN(updated_credit, group_credit); |
| } |
| } |
| |
| credit = (updated_credit < 0) ? 0 : updated_credit; |
| |
| return credit; |
| } |
| |
| void ol_tx_txq_group_credit_update( |
| struct ol_txrx_pdev_t *pdev, |
| struct ol_tx_frms_queue_t *txq, |
| int32_t credit, |
| u_int8_t absolute) |
| { |
| u_int8_t i; |
| /* |
| * If this tx queue belongs to a group then |
| * update group credit |
| */ |
| for (i = 0; i < OL_TX_MAX_GROUPS_PER_QUEUE; i++) { |
| if (txq->group_ptrs[i]) { |
| ol_txrx_update_group_credit(txq->group_ptrs[i], credit, absolute); |
| } |
| } |
| OL_TX_UPDATE_GROUP_CREDIT_STATS(pdev); |
| } |
| |
| void |
| ol_tx_set_vdev_group_ptr( |
| ol_txrx_pdev_handle pdev, |
| u_int8_t vdev_id, |
| struct ol_tx_queue_group_t *grp_ptr) |
| { |
| struct ol_txrx_vdev_t *vdev = NULL; |
| struct ol_txrx_peer_t *peer = NULL; |
| |
| TAILQ_FOREACH(vdev, &pdev->vdev_list, vdev_list_elem) { |
| if (vdev->vdev_id == vdev_id) { |
| u_int8_t i, j; |
| /* update vdev queues group pointers */ |
| for (i = 0; i < OL_TX_VDEV_NUM_QUEUES; i++) { |
| for (j = 0; j < OL_TX_MAX_GROUPS_PER_QUEUE; j++) { |
| vdev->txqs[i].group_ptrs[j] = grp_ptr; |
| } |
| } |
| adf_os_spin_lock_bh(&pdev->peer_ref_mutex); |
| /* Update peer queue group pointers */ |
| TAILQ_FOREACH(peer, &vdev->peer_list, peer_list_elem) { |
| for (i = 0; i < OL_TX_NUM_TIDS; i++) { |
| for (j = 0; j < OL_TX_MAX_GROUPS_PER_QUEUE; j++) { |
| peer->txqs[i].group_ptrs[j] = grp_ptr; |
| } |
| } |
| } |
| adf_os_spin_unlock_bh(&pdev->peer_ref_mutex); |
| break; |
| } |
| } |
| } |
| |
| void |
| ol_tx_txq_set_group_ptr( |
| struct ol_tx_frms_queue_t *txq, |
| struct ol_tx_queue_group_t *grp_ptr) |
| { |
| u_int8_t i; |
| for (i = 0; i < OL_TX_MAX_GROUPS_PER_QUEUE; i++) { |
| txq->group_ptrs[i] = grp_ptr; |
| } |
| } |
| |
| void |
| ol_tx_set_peer_group_ptr( |
| ol_txrx_pdev_handle pdev, |
| struct ol_txrx_peer_t *peer, |
| u_int8_t vdev_id, |
| u_int8_t tid) |
| { |
| u_int8_t i, j = 0; |
| struct ol_tx_queue_group_t *group = NULL; |
| |
| for (i = 0; i < OL_TX_MAX_GROUPS_PER_QUEUE; i++) { |
| peer->txqs[tid].group_ptrs[i] = NULL; |
| } |
| for (i = 0; i < OL_TX_MAX_TXQ_GROUPS; i++) { |
| group = &pdev->txq_grps[i]; |
| if (ol_tx_vdev_has_tx_queue_group(group, vdev_id)) { |
| if (tid < OL_TX_NUM_QOS_TIDS) { |
| if (ol_tx_ac_has_tx_queue_group( |
| group, TXRX_TID_TO_WMM_AC(tid))) { |
| peer->txqs[tid].group_ptrs[j] = group; |
| j++; |
| } |
| } else { |
| peer->txqs[tid].group_ptrs[j] = group; |
| j++; |
| } |
| } |
| if (j >= OL_TX_MAX_GROUPS_PER_QUEUE) { |
| break; |
| } |
| } |
| } |
| |
| u_int32_t ol_tx_get_max_tx_groups_supported(struct ol_txrx_pdev_t *pdev) |
| { |
| #ifdef HIF_SDIO |
| return OL_TX_MAX_TXQ_GROUPS; |
| #else |
| return 0; |
| #endif |
| } |
| #endif |