summaryrefslogtreecommitdiff
path: root/drivers/net/wireless/eswin/ecrnx_txq.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/wireless/eswin/ecrnx_txq.c')
-rw-r--r--drivers/net/wireless/eswin/ecrnx_txq.c1772
1 files changed, 1772 insertions, 0 deletions
diff --git a/drivers/net/wireless/eswin/ecrnx_txq.c b/drivers/net/wireless/eswin/ecrnx_txq.c
new file mode 100644
index 000000000000..80536b907e7c
--- /dev/null
+++ b/drivers/net/wireless/eswin/ecrnx_txq.c
@@ -0,0 +1,1772 @@
+/**
+ ******************************************************************************
+ *
+ * @file ecrnx_txq.c
+ *
+ * Copyright (C) ESWIN 2015-2020
+ *
+ ******************************************************************************
+ */
+
+#include "ecrnx_defs.h"
+#include "ecrnx_tx.h"
+#include "ipc_host.h"
+#include "ecrnx_events.h"
+
+/******************************************************************************
+ * Utils functions
+ *****************************************************************************/
+#ifdef CONFIG_ECRNX_SOFTMAC
+const int nx_tid_prio[NX_NB_TID_PER_STA] = {15, 7, 14, 6, 13, 5, 12, 4,
+ 11, 3, 8, 0, 10, 2, 9, 1};
+
+static inline int ecrnx_txq_sta_idx(struct ecrnx_sta *sta, u8 tid)
+{
+ return sta->sta_idx * NX_NB_TXQ_PER_STA;
+}
+
+static inline int ecrnx_txq_vif_idx(struct ecrnx_vif *vif, u8 ac)
+{
+ return vif->vif_index * NX_NB_TXQ_PER_VIF + ac + NX_FIRST_VIF_TXQ_IDX;
+}
+
+#ifdef CONFIG_MAC80211_TXQ
+struct ecrnx_txq *ecrnx_txq_sta_get(struct ecrnx_sta *ecrnx_sta, u8 tid)
+{
+ struct ieee80211_sta *sta = ecrnx_to_ieee80211_sta(ecrnx_sta);
+ struct ieee80211_txq *mac_txq = sta->txq[tid];
+
+ return (struct ecrnx_txq *)mac_txq->drv_priv;
+}
+
+struct ecrnx_txq *ecrnx_txq_vif_get(struct ecrnx_vif *ecrnx_vif, u8 ac)
+{
+ struct ieee80211_vif *vif = ecrnx_to_ieee80211_vif(ecrnx_vif);
+
+ /* mac80211 only allocate one txq per vif for Best Effort */
+ if (ac == ECRNX_HWQ_BE) {
+ struct ieee80211_txq *mac_txq = vif->txq;
+ if (!mac_txq)
+ return NULL;
+ return (struct ecrnx_txq *)mac_txq->drv_priv;
+ }
+
+ if (ac > NX_TXQ_CNT)
+ ac = ECRNX_HWQ_BK;
+
+ return &ecrnx_vif->txqs[ac];
+}
+
+#else /* ! CONFIG_MAC80211_TXQ */
+struct ecrnx_txq *ecrnx_txq_sta_get(struct ecrnx_sta *sta, u8 tid)
+{
+ if (tid >= NX_NB_TXQ_PER_STA)
+ tid = 0;
+
+ return &sta->txqs[tid];
+}
+
+struct ecrnx_txq *ecrnx_txq_vif_get(struct ecrnx_vif *vif, u8 ac)
+{
+ if (ac > NX_TXQ_CNT)
+ ac = ECRNX_HWQ_BK;
+
+ return &vif->txqs[ac];
+}
+
+#endif /* CONFIG_MAC80211_TXQ */
+
+static inline struct ecrnx_sta *ecrnx_txq_2_sta(struct ecrnx_txq *txq)
+{
+ if (txq->sta)
+ return (struct ecrnx_sta *)txq->sta->drv_priv;
+ return NULL;
+}
+
+#else /* CONFIG_ECRNX_FULLMAC */
+const int nx_tid_prio[NX_NB_TID_PER_STA] = {7, 6, 5, 4, 3, 0, 2, 1};
+
+static inline int ecrnx_txq_sta_idx(struct ecrnx_sta *sta, u8 tid)
+{
+ if (is_multicast_sta(sta->sta_idx))
+ return NX_FIRST_VIF_TXQ_IDX + sta->vif_idx;
+ else
+ return (sta->sta_idx * NX_NB_TXQ_PER_STA) + tid;
+}
+
+static inline int ecrnx_txq_vif_idx(struct ecrnx_vif *vif, u8 type)
+{
+ return NX_FIRST_VIF_TXQ_IDX + master_vif_idx(vif) + (type * NX_VIRT_DEV_MAX);
+}
+
+struct ecrnx_txq *ecrnx_txq_sta_get(struct ecrnx_sta *sta, u8 tid,
+ struct ecrnx_hw * ecrnx_hw)
+{
+ if (tid >= NX_NB_TXQ_PER_STA)
+ tid = 0;
+
+ return &ecrnx_hw->txq[ecrnx_txq_sta_idx(sta, tid)];
+}
+
+struct ecrnx_txq *ecrnx_txq_vif_get(struct ecrnx_vif *vif, u8 type)
+{
+ if (type > NX_UNK_TXQ_TYPE)
+ type = NX_BCMC_TXQ_TYPE;
+
+ return &vif->ecrnx_hw->txq[ecrnx_txq_vif_idx(vif, type)];
+}
+
+static inline struct ecrnx_sta *ecrnx_txq_2_sta(struct ecrnx_txq *txq)
+{
+ return txq->sta;
+}
+
+#endif /* CONFIG_ECRNX_SOFTMAC */
+
+
+/******************************************************************************
+ * Init/Deinit functions
+ *****************************************************************************/
+/**
+ * ecrnx_txq_init - Initialize a TX queue
+ *
+ * @txq: TX queue to be initialized
+ * @idx: TX queue index
+ * @status: TX queue initial status
+ * @hwq: Associated HW queue
+ * @ndev: Net device this queue belongs to
+ * (may be null for non netdev txq)
+ *
+ * Each queue is initialized with the credit of @NX_TXQ_INITIAL_CREDITS.
+ */
+static void ecrnx_txq_init(struct ecrnx_txq *txq, int idx, u8 status,
+ struct ecrnx_hwq *hwq, int tid,
+#ifdef CONFIG_ECRNX_SOFTMAC
+ struct ieee80211_sta *sta
+#else
+ struct ecrnx_sta *sta, struct net_device *ndev
+#endif
+ )
+{
+ int i;
+
+ txq->idx = idx;
+ txq->status = status;
+ txq->credits = NX_TXQ_INITIAL_CREDITS;
+ txq->pkt_sent = 0;
+ skb_queue_head_init(&txq->sk_list);
+ txq->last_retry_skb = NULL;
+ txq->nb_retry = 0;
+ txq->hwq = hwq;
+ txq->sta = sta;
+ for (i = 0; i < CONFIG_USER_MAX ; i++)
+ txq->pkt_pushed[i] = 0;
+ txq->push_limit = 0;
+ txq->tid = tid;
+#ifdef CONFIG_MAC80211_TXQ
+ txq->nb_ready_mac80211 = 0;
+#endif
+#ifdef CONFIG_ECRNX_SOFTMAC
+ txq->baw.agg_on = false;
+#ifdef CONFIG_ECRNX_AMSDUS_TX
+ txq->amsdu_anchor = NULL;
+ txq->amsdu_ht_len_cap = 0;
+ txq->amsdu_vht_len_cap = 0;
+#endif /* CONFIG_ECRNX_AMSDUS_TX */
+
+#else /* ! CONFIG_ECRNX_SOFTMAC */
+ txq->ps_id = LEGACY_PS_ID;
+ if (idx < NX_FIRST_VIF_TXQ_IDX) {
+ int sta_idx = sta->sta_idx;
+ int tid = idx - (sta_idx * NX_NB_TXQ_PER_STA);
+ if (tid < NX_NB_TID_PER_STA)
+ txq->ndev_idx = NX_STA_NDEV_IDX(tid, sta_idx);
+ else
+ txq->ndev_idx = NDEV_NO_TXQ;
+ } else if (idx < NX_FIRST_UNK_TXQ_IDX) {
+ txq->ndev_idx = NX_BCMC_TXQ_NDEV_IDX;
+ } else {
+ txq->ndev_idx = NDEV_NO_TXQ;
+ }
+ txq->ndev = ndev;
+#ifdef CONFIG_ECRNX_AMSDUS_TX
+ txq->amsdu = NULL;
+ txq->amsdu_len = 0;
+#endif /* CONFIG_ECRNX_AMSDUS_TX */
+#endif /* CONFIG_ECRNX_SOFTMAC */
+}
+
+/**
+ * ecrnx_txq_flush - Flush all buffers queued for a TXQ
+ *
+ * @ecrnx_hw: main driver data
+ * @txq: txq to flush
+ */
+void ecrnx_txq_drop_skb(struct ecrnx_txq *txq, struct sk_buff *skb, struct ecrnx_hw *ecrnx_hw, bool retry_packet)
+{
+ struct ecrnx_sw_txhdr *sw_txhdr;
+ unsigned long queued_time = 0;
+
+ skb_unlink(skb, &txq->sk_list);
+
+ sw_txhdr = ((struct ecrnx_txhdr *)skb->data)->sw_hdr;
+ /* hwq->len doesn't count skb to retry */
+#ifdef CONFIG_ECRNX_FULLMAC
+ queued_time = jiffies - sw_txhdr->jiffies;
+#endif /* CONFIG_ECRNX_SOFTMAC*/
+ trace_txq_drop_skb(skb, txq, queued_time);
+
+#ifdef CONFIG_ECRNX_AMSDUS_TX
+ if (sw_txhdr->desc.host.packet_cnt > 1) {
+ struct ecrnx_amsdu_txhdr *amsdu_txhdr;
+ list_for_each_entry(amsdu_txhdr, &sw_txhdr->amsdu.hdrs, list) {
+#ifndef CONFIG_ECRNX_ESWIN
+ dma_unmap_single(ecrnx_hw->dev, amsdu_txhdr->dma_addr,
+ amsdu_txhdr->map_len, DMA_TO_DEVICE);
+#endif
+ dev_kfree_skb_any(amsdu_txhdr->skb);
+ }
+#ifdef CONFIG_ECRNX_FULLMAC
+ if (txq->amsdu == sw_txhdr)
+ txq->amsdu = NULL;
+#endif
+ }
+#endif
+ kmem_cache_free(ecrnx_hw->sw_txhdr_cache, sw_txhdr);
+#ifndef CONFIG_ECRNX_ESWIN
+ dma_unmap_single(ecrnx_hw->dev, sw_txhdr->dma_addr, sw_txhdr->map_len,
+ DMA_TO_DEVICE);
+#endif
+ if (retry_packet) {
+ txq->nb_retry--;
+ if (txq->nb_retry == 0) {
+ WARN(skb != txq->last_retry_skb,
+ "last dropped retry buffer is not the expected one");
+ txq->last_retry_skb = NULL;
+ }
+ }
+#ifdef CONFIG_ECRNX_SOFTMAC
+ else
+ txq->hwq->len --; // hwq->len doesn't count skb to retry
+ ieee80211_free_txskb(ecrnx_hw->hw, skb);
+#else
+ dev_kfree_skb_any(skb);
+#endif /* CONFIG_ECRNX_SOFTMAC */
+}
+/**
+ * ecrnx_txq_flush - Flush all buffers queued for a TXQ
+ *
+ * @ecrnx_hw: main driver data
+ * @txq: txq to flush
+ */
+void ecrnx_txq_flush(struct ecrnx_hw *ecrnx_hw, struct ecrnx_txq *txq)
+{
+ int i, pushed = 0;
+
+ while(!skb_queue_empty(&txq->sk_list)) {
+ ecrnx_txq_drop_skb(txq, skb_peek(&txq->sk_list), ecrnx_hw, txq->nb_retry);
+ }
+
+ for (i = 0; i < CONFIG_USER_MAX; i++) {
+ pushed += txq->pkt_pushed[i];
+ }
+
+ if (pushed)
+ dev_warn(ecrnx_hw->dev, "TXQ[%d]: %d skb still pushed to the FW",
+ txq->idx, pushed);
+}
+
+/**
+ * ecrnx_txq_deinit - De-initialize a TX queue
+ *
+ * @ecrnx_hw: Driver main data
+ * @txq: TX queue to be de-initialized
+ * Any buffer stuck in a queue will be freed.
+ */
+static void ecrnx_txq_deinit(struct ecrnx_hw *ecrnx_hw, struct ecrnx_txq *txq)
+{
+ if (txq->idx == TXQ_INACTIVE)
+ return;
+
+ spin_lock_bh(&ecrnx_hw->tx_lock);
+ ecrnx_txq_del_from_hw_list(txq);
+ txq->idx = TXQ_INACTIVE;
+ spin_unlock_bh(&ecrnx_hw->tx_lock);
+
+ ecrnx_txq_flush(ecrnx_hw, txq);
+}
+
+/**
+ * ecrnx_txq_vif_init - Initialize all TXQ linked to a vif
+ *
+ * @ecrnx_hw: main driver data
+ * @ecrnx_vif: Pointer on VIF
+ * @status: Intial txq status
+ *
+ * Softmac : 1 VIF TXQ per HWQ
+ *
+ * Fullmac : 1 VIF TXQ for BC/MC
+ * 1 VIF TXQ for MGMT to unknown STA
+ */
+void ecrnx_txq_vif_init(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *ecrnx_vif,
+ u8 status)
+{
+ struct ecrnx_txq *txq;
+ int idx;
+
+#ifdef CONFIG_ECRNX_SOFTMAC
+ int ac;
+
+ idx = ecrnx_txq_vif_idx(ecrnx_vif, 0);
+ foreach_vif_txq(ecrnx_vif, txq, ac) {
+ if (txq) {
+ ecrnx_txq_init(txq, idx, status, &ecrnx_hw->hwq[ac], 0, NULL);
+#ifdef CONFIG_MAC80211_TXQ
+ if (ac != ECRNX_HWQ_BE)
+ txq->nb_ready_mac80211 = NOT_MAC80211_TXQ;
+#endif
+ }
+ idx++;
+ }
+
+#else
+ txq = ecrnx_txq_vif_get(ecrnx_vif, NX_BCMC_TXQ_TYPE);
+ idx = ecrnx_txq_vif_idx(ecrnx_vif, NX_BCMC_TXQ_TYPE);
+ ecrnx_txq_init(txq, idx, status, &ecrnx_hw->hwq[ECRNX_HWQ_BE], 0,
+ &ecrnx_hw->sta_table[ecrnx_vif->ap.bcmc_index], ecrnx_vif->ndev);
+
+ txq = ecrnx_txq_vif_get(ecrnx_vif, NX_UNK_TXQ_TYPE);
+ idx = ecrnx_txq_vif_idx(ecrnx_vif, NX_UNK_TXQ_TYPE);
+ ecrnx_txq_init(txq, idx, status, &ecrnx_hw->hwq[ECRNX_HWQ_VO], TID_MGT,
+ NULL, ecrnx_vif->ndev);
+
+#endif /* CONFIG_ECRNX_SOFTMAC */
+}
+
+/**
+ * ecrnx_txq_vif_deinit - Deinitialize all TXQ linked to a vif
+ *
+ * @ecrnx_hw: main driver data
+ * @ecrnx_vif: Pointer on VIF
+ */
+void ecrnx_txq_vif_deinit(struct ecrnx_hw * ecrnx_hw, struct ecrnx_vif *ecrnx_vif)
+{
+ struct ecrnx_txq *txq;
+
+#ifdef CONFIG_ECRNX_SOFTMAC
+ int ac;
+
+ foreach_vif_txq(ecrnx_vif, txq, ac) {
+ if (txq)
+ ecrnx_txq_deinit(ecrnx_hw, txq);
+ }
+
+#else
+ txq = ecrnx_txq_vif_get(ecrnx_vif, NX_BCMC_TXQ_TYPE);
+ ecrnx_txq_deinit(ecrnx_hw, txq);
+
+ txq = ecrnx_txq_vif_get(ecrnx_vif, NX_UNK_TXQ_TYPE);
+ ecrnx_txq_deinit(ecrnx_hw, txq);
+
+#endif /* CONFIG_ECRNX_SOFTMAC */
+}
+
+
+/**
+ * ecrnx_txq_sta_init - Initialize TX queues for a STA
+ *
+ * @ecrnx_hw: Main driver data
+ * @ecrnx_sta: STA for which tx queues need to be initialized
+ * @status: Intial txq status
+ *
+ * This function initialize all the TXQ associated to a STA.
+ * Softmac : 1 TXQ per TID
+ *
+ * Fullmac : 1 TXQ per TID (limited to 8)
+ * 1 TXQ for MGMT
+ */
+void ecrnx_txq_sta_init(struct ecrnx_hw *ecrnx_hw, struct ecrnx_sta *ecrnx_sta,
+ u8 status)
+{
+ struct ecrnx_txq *txq;
+ int tid, idx;
+
+#ifdef CONFIG_ECRNX_SOFTMAC
+ struct ieee80211_sta *sta = ecrnx_to_ieee80211_sta(ecrnx_sta);
+ idx = ecrnx_txq_sta_idx(ecrnx_sta, 0);
+
+ foreach_sta_txq(ecrnx_sta, txq, tid, ecrnx_hw) {
+ ecrnx_txq_init(txq, idx, status, &ecrnx_hw->hwq[ecrnx_tid2hwq[tid]], tid, sta);
+ idx++;
+ }
+
+#else
+ struct ecrnx_vif *ecrnx_vif = ecrnx_hw->vif_table[ecrnx_sta->vif_idx];
+ idx = ecrnx_txq_sta_idx(ecrnx_sta, 0);
+
+ foreach_sta_txq(ecrnx_sta, txq, tid, ecrnx_hw) {
+ ecrnx_txq_init(txq, idx, status, &ecrnx_hw->hwq[ecrnx_tid2hwq[tid]], tid,
+ ecrnx_sta, ecrnx_vif->ndev);
+ txq->ps_id = ecrnx_sta->uapsd_tids & (1 << tid) ? UAPSD_ID : LEGACY_PS_ID;
+ idx++;
+ }
+
+#endif /* CONFIG_ECRNX_SOFTMAC*/
+#ifndef CONFIG_ECRNX_ESWIN
+ ecrnx_ipc_sta_buffer_init(ecrnx_hw, ecrnx_sta->sta_idx);
+#endif
+}
+
+/**
+ * ecrnx_txq_sta_deinit - Deinitialize TX queues for a STA
+ *
+ * @ecrnx_hw: Main driver data
+ * @ecrnx_sta: STA for which tx queues need to be deinitialized
+ */
+void ecrnx_txq_sta_deinit(struct ecrnx_hw *ecrnx_hw, struct ecrnx_sta *ecrnx_sta)
+{
+ struct ecrnx_txq *txq;
+ int tid;
+
+ foreach_sta_txq(ecrnx_sta, txq, tid, ecrnx_hw) {
+ ecrnx_txq_deinit(ecrnx_hw, txq);
+ }
+}
+
+#ifdef CONFIG_ECRNX_FULLMAC
+/**
+ * ecrnx_txq_unk_vif_init - Initialize TXQ for unknown STA linked to a vif
+ *
+ * @ecrnx_vif: Pointer on VIF
+ */
+void ecrnx_txq_unk_vif_init(struct ecrnx_vif *ecrnx_vif)
+{
+ struct ecrnx_hw *ecrnx_hw = ecrnx_vif->ecrnx_hw;
+ struct ecrnx_txq *txq;
+ int idx;
+
+ txq = ecrnx_txq_vif_get(ecrnx_vif, NX_UNK_TXQ_TYPE);
+ idx = ecrnx_txq_vif_idx(ecrnx_vif, NX_UNK_TXQ_TYPE);
+ ecrnx_txq_init(txq, idx, 0, &ecrnx_hw->hwq[ECRNX_HWQ_VO], TID_MGT, NULL, ecrnx_vif->ndev);
+}
+
+/**
+ * ecrnx_txq_tdls_vif_deinit - Deinitialize TXQ for unknown STA linked to a vif
+ *
+ * @ecrnx_vif: Pointer on VIF
+ */
+void ecrnx_txq_unk_vif_deinit(struct ecrnx_vif *ecrnx_vif)
+{
+ struct ecrnx_txq *txq;
+
+ txq = ecrnx_txq_vif_get(ecrnx_vif, NX_UNK_TXQ_TYPE);
+ ecrnx_txq_deinit(ecrnx_vif->ecrnx_hw, txq);
+}
+
+/**
+ * ecrnx_init_unk_txq - Initialize TX queue for the transmission on a offchannel
+ *
+ * @vif: Interface for which the queue has to be initialized
+ *
+ * NOTE: Offchannel txq is only active for the duration of the ROC
+ */
+void ecrnx_txq_offchan_init(struct ecrnx_vif *ecrnx_vif)
+{
+ struct ecrnx_hw *ecrnx_hw = ecrnx_vif->ecrnx_hw;
+ struct ecrnx_txq *txq;
+
+ txq = &ecrnx_hw->txq[NX_OFF_CHAN_TXQ_IDX];
+ ecrnx_txq_init(txq, NX_OFF_CHAN_TXQ_IDX, ECRNX_TXQ_STOP_CHAN,
+ &ecrnx_hw->hwq[ECRNX_HWQ_VO], TID_MGT, NULL, ecrnx_vif->ndev);
+}
+
+/**
+ * ecrnx_deinit_offchan_txq - Deinitialize TX queue for offchannel
+ *
+ * @vif: Interface that manages the STA
+ *
+ * This function deintialize txq for one STA.
+ * Any buffer stuck in a queue will be freed.
+ */
+void ecrnx_txq_offchan_deinit(struct ecrnx_vif *ecrnx_vif)
+{
+ struct ecrnx_txq *txq;
+
+ txq = &ecrnx_vif->ecrnx_hw->txq[NX_OFF_CHAN_TXQ_IDX];
+ ecrnx_txq_deinit(ecrnx_vif->ecrnx_hw, txq);
+}
+
+
+/**
+ * ecrnx_txq_tdls_vif_init - Initialize TXQ vif for TDLS
+ *
+ * @ecrnx_vif: Pointer on VIF
+ */
+void ecrnx_txq_tdls_vif_init(struct ecrnx_vif *ecrnx_vif)
+{
+ struct ecrnx_hw *ecrnx_hw = ecrnx_vif->ecrnx_hw;
+
+ if (!(ecrnx_hw->wiphy->flags & WIPHY_FLAG_SUPPORTS_TDLS))
+ return;
+
+ ecrnx_txq_unk_vif_init(ecrnx_vif);
+}
+
+/**
+ * ecrnx_txq_tdls_vif_deinit - Deinitialize TXQ vif for TDLS
+ *
+ * @ecrnx_vif: Pointer on VIF
+ */
+void ecrnx_txq_tdls_vif_deinit(struct ecrnx_vif *ecrnx_vif)
+{
+ struct ecrnx_hw *ecrnx_hw = ecrnx_vif->ecrnx_hw;
+
+ if (!(ecrnx_hw->wiphy->flags & WIPHY_FLAG_SUPPORTS_TDLS))
+ return;
+
+ ecrnx_txq_unk_vif_deinit(ecrnx_vif);
+}
+/**
+ * ecrnx_txq_drop_old_traffic - Drop pkt queued for too long in a TXQ
+ *
+ * @txq: TXQ to process
+ * @ecrnx_hw: Driver main data
+ * @skb_timeout: Max queue duration, in jiffies, for this queue
+ * @dropped: Updated to inidicate if at least one skb was dropped
+ *
+ * @return Whether there is still pkt queued in this queue.
+ */
+static bool ecrnx_txq_drop_old_traffic(struct ecrnx_txq *txq, struct ecrnx_hw *ecrnx_hw,
+ unsigned long skb_timeout, bool *dropped)
+{
+ struct sk_buff *skb, *skb_next;
+ bool pkt_queued = false;
+ int retry_packet = txq->nb_retry;
+
+ if (txq->idx == TXQ_INACTIVE)
+ return false;
+
+ spin_lock(&ecrnx_hw->tx_lock);
+
+ skb_queue_walk_safe(&txq->sk_list, skb, skb_next) {
+
+ struct ecrnx_sw_txhdr *sw_txhdr;
+
+ if (retry_packet) {
+ // Don't drop retry packets
+ retry_packet--;
+ continue;
+ }
+
+ sw_txhdr = ((struct ecrnx_txhdr *)skb->data)->sw_hdr;
+
+ if (!time_after(jiffies, sw_txhdr->jiffies + skb_timeout)) {
+ pkt_queued = true;
+ break;
+ }
+
+ *dropped = true;
+ ECRNX_WARN("%s:skb:0x%08x,txq:0x%p, txq_idx:%d, ps_active:%d, ps_id:%d \n", __func__, skb, txq, txq->idx, txq->sta->ps.active, txq->ps_id);
+ ecrnx_txq_drop_skb(txq, skb, ecrnx_hw, false);
+ if (txq->sta && txq->sta->ps.active) {
+ txq->sta->ps.pkt_ready[txq->ps_id]--;
+ if (txq->sta->ps.pkt_ready[txq->ps_id] == 0)
+ ecrnx_set_traffic_status(ecrnx_hw, txq->sta, false, txq->ps_id);
+
+ // drop packet during PS service period
+ if (txq->sta->ps.sp_cnt[txq->ps_id]) {
+ txq->sta->ps.sp_cnt[txq->ps_id] --;
+ if (txq->push_limit)
+ txq->push_limit--;
+ if (WARN(((txq->ps_id == UAPSD_ID) &&
+ (txq->sta->ps.sp_cnt[txq->ps_id] == 0)),
+ "Drop last packet of UAPSD service period")) {
+ // TODO: inform FW to end SP
+ }
+ }
+ trace_ps_drop(txq->sta);
+ }
+ }
+
+ if (skb_queue_empty(&txq->sk_list)) {
+ ecrnx_txq_del_from_hw_list(txq);
+ txq->pkt_sent = 0;
+ }
+
+ spin_unlock(&ecrnx_hw->tx_lock);
+
+#ifdef CONFIG_ECRNX_FULLMAC
+ /* restart netdev queue if number no more queued buffer */
+ if (unlikely(txq->status & ECRNX_TXQ_NDEV_FLOW_CTRL) &&
+ skb_queue_empty(&txq->sk_list)) {
+ txq->status &= ~ECRNX_TXQ_NDEV_FLOW_CTRL;
+ netif_wake_subqueue(txq->ndev, txq->ndev_idx);
+ trace_txq_flowctrl_restart(txq);
+ }
+#endif /* CONFIG_ECRNX_FULLMAC */
+
+ return pkt_queued;
+}
+
+/**
+ * ecrnx_txq_drop_ap_vif_old_traffic - Drop pkt queued for too long in TXQs
+ * linked to an "AP" vif (AP, MESH, P2P_GO)
+ *
+ * @vif: Vif to process
+ * @return Whether there is still pkt queued in any TXQ.
+ */
+static bool ecrnx_txq_drop_ap_vif_old_traffic(struct ecrnx_vif *vif)
+{
+ struct ecrnx_sta *sta;
+ unsigned long timeout = (vif->ap.bcn_interval * HZ * 3) >> 10;
+ bool pkt_queued = false;
+ bool pkt_dropped = false;
+
+ // Should never be needed but still check VIF queues
+ ecrnx_txq_drop_old_traffic(ecrnx_txq_vif_get(vif, NX_BCMC_TXQ_TYPE),
+ vif->ecrnx_hw, ECRNX_TXQ_MAX_QUEUE_JIFFIES, &pkt_dropped);
+ ecrnx_txq_drop_old_traffic(ecrnx_txq_vif_get(vif, NX_UNK_TXQ_TYPE),
+ vif->ecrnx_hw, ECRNX_TXQ_MAX_QUEUE_JIFFIES, &pkt_dropped);
+ ECRNX_WARN("Dropped packet in BCMC/UNK queue. \n");
+
+ list_for_each_entry(sta, &vif->ap.sta_list, list) {
+ struct ecrnx_txq *txq;
+ int tid;
+ foreach_sta_txq(sta, txq, tid, vif->ecrnx_hw) {
+ pkt_queued |= ecrnx_txq_drop_old_traffic(txq, vif->ecrnx_hw,
+ timeout * sta->listen_interval,
+ &pkt_dropped);
+ }
+ }
+
+ return pkt_queued;
+}
+
+/**
+ * ecrnx_txq_drop_sta_vif_old_traffic - Drop pkt queued for too long in TXQs
+ * linked to a "STA" vif. In theory this should not be required as there is no
+ * case where traffic can accumulate in a STA interface.
+ *
+ * @vif: Vif to process
+ * @return Whether there is still pkt queued in any TXQ.
+ */
+static bool ecrnx_txq_drop_sta_vif_old_traffic(struct ecrnx_vif *vif)
+{
+ struct ecrnx_txq *txq;
+ bool pkt_queued = false, pkt_dropped = false;
+ int tid;
+
+ if (vif->tdls_status == TDLS_LINK_ACTIVE) {
+ txq = ecrnx_txq_vif_get(vif, NX_UNK_TXQ_TYPE);
+ pkt_queued |= ecrnx_txq_drop_old_traffic(txq, vif->ecrnx_hw,
+ ECRNX_TXQ_MAX_QUEUE_JIFFIES,
+ &pkt_dropped);
+ foreach_sta_txq(vif->sta.tdls_sta, txq, tid, vif->ecrnx_hw) {
+ pkt_queued |= ecrnx_txq_drop_old_traffic(txq, vif->ecrnx_hw,
+ ECRNX_TXQ_MAX_QUEUE_JIFFIES,
+ &pkt_dropped);
+ }
+ }
+
+ if (vif->sta.ap) {
+ foreach_sta_txq(vif->sta.ap, txq, tid, vif->ecrnx_hw) {
+ pkt_queued |= ecrnx_txq_drop_old_traffic(txq, vif->ecrnx_hw,
+ ECRNX_TXQ_MAX_QUEUE_JIFFIES,
+ &pkt_dropped);
+ }
+ }
+
+ if (pkt_dropped) {
+ ECRNX_WARN("Dropped packet in STA interface TXQs. \n");
+ }
+ return pkt_queued;
+}
+
+/**
+ * ecrnx_txq_cleanup_timer_cb - callack for TXQ cleaup timer
+ * Used to prevent pkt to accumulate in TXQ. The main use case is for AP
+ * interface with client in Power Save mode but just in case all TXQs are
+ * checked.
+ *
+ * @t: timer structure
+ */
+static void ecrnx_txq_cleanup_timer_cb(struct timer_list *t)
+{
+ struct ecrnx_hw *ecrnx_hw = from_timer(ecrnx_hw, t, txq_cleanup);
+ struct ecrnx_vif *vif;
+ bool pkt_queue = false;
+
+ list_for_each_entry(vif, &ecrnx_hw->vifs, list) {
+ switch (ECRNX_VIF_TYPE(vif)) {
+ case NL80211_IFTYPE_AP:
+ case NL80211_IFTYPE_P2P_GO:
+ case NL80211_IFTYPE_MESH_POINT:
+ pkt_queue |= ecrnx_txq_drop_ap_vif_old_traffic(vif);
+ break;
+ case NL80211_IFTYPE_STATION:
+ case NL80211_IFTYPE_P2P_CLIENT:
+ pkt_queue |= ecrnx_txq_drop_sta_vif_old_traffic(vif);
+ break;
+ case NL80211_IFTYPE_AP_VLAN:
+ case NL80211_IFTYPE_MONITOR:
+ default:
+ continue;
+ }
+ }
+
+ if (pkt_queue)
+ mod_timer(t, jiffies + ECRNX_TXQ_CLEANUP_INTERVAL);
+}
+
+/**
+ * ecrnx_txq_start_cleanup_timer - Start 'cleanup' timer if not started
+ *
+ * @ecrnx_hw: Driver main data
+ */
+void ecrnx_txq_start_cleanup_timer(struct ecrnx_hw *ecrnx_hw, struct ecrnx_sta *sta)
+{
+ if (sta && !is_multicast_sta(sta->sta_idx) &&
+ !timer_pending(&ecrnx_hw->txq_cleanup))
+ mod_timer(&ecrnx_hw->txq_cleanup, jiffies + ECRNX_TXQ_CLEANUP_INTERVAL);
+}
+
+/**
+ * ecrnx_txq_prepare - Global initialization of txq
+ *
+ * @ecrnx_hw: Driver main data
+ */
+void ecrnx_txq_prepare(struct ecrnx_hw *ecrnx_hw)
+{
+ int i;
+
+ for (i = 0; i < NX_NB_TXQ; i++) {
+ ecrnx_hw->txq[i].idx = TXQ_INACTIVE;
+ }
+
+ timer_setup(&ecrnx_hw->txq_cleanup, ecrnx_txq_cleanup_timer_cb, 0);
+}
+#endif
+
+/******************************************************************************
+ * Start/Stop functions
+ *****************************************************************************/
+/**
+ * ecrnx_txq_add_to_hw_list - Add TX queue to a HW queue schedule list.
+ *
+ * @txq: TX queue to add
+ *
+ * Add the TX queue if not already present in the HW queue list.
+ * To be called with tx_lock hold
+ */
+void ecrnx_txq_add_to_hw_list(struct ecrnx_txq *txq)
+{
+ if (!(txq->status & ECRNX_TXQ_IN_HWQ_LIST)) {
+ trace_txq_add_to_hw(txq);
+ txq->status |= ECRNX_TXQ_IN_HWQ_LIST;
+ list_add_tail(&txq->sched_list, &txq->hwq->list);
+ txq->hwq->need_processing = true;
+ }
+}
+
+/**
+ * ecrnx_txq_del_from_hw_list - Delete TX queue from a HW queue schedule list.
+ *
+ * @txq: TX queue to delete
+ *
+ * Remove the TX queue from the HW queue list if present.
+ * To be called with tx_lock hold
+ */
+void ecrnx_txq_del_from_hw_list(struct ecrnx_txq *txq)
+{
+ if (txq->status & ECRNX_TXQ_IN_HWQ_LIST) {
+ trace_txq_del_from_hw(txq);
+ txq->status &= ~ECRNX_TXQ_IN_HWQ_LIST;
+ list_del(&txq->sched_list);
+ }
+}
+
+/**
+ * ecrnx_txq_skb_ready - Check if skb are available for the txq
+ *
+ * @txq: Pointer on txq
+ * @return True if there are buffer ready to be pushed on this txq,
+ * false otherwise
+ */
+static inline bool ecrnx_txq_skb_ready(struct ecrnx_txq *txq)
+{
+#ifdef CONFIG_MAC80211_TXQ
+ if (txq->nb_ready_mac80211 != NOT_MAC80211_TXQ)
+ return ((txq->nb_ready_mac80211 > 0) || !skb_queue_empty(&txq->sk_list));
+ else
+#endif
+ return !skb_queue_empty(&txq->sk_list);
+}
+
+/**
+ * ecrnx_txq_start - Try to Start one TX queue
+ *
+ * @txq: TX queue to start
+ * @reason: reason why the TX queue is started (among ECRNX_TXQ_STOP_xxx)
+ *
+ * Re-start the TX queue for one reason.
+ * If after this the txq is no longer stopped and some buffers are ready,
+ * the TX queue is also added to HW queue list.
+ * To be called with tx_lock hold
+ */
+void ecrnx_txq_start(struct ecrnx_txq *txq, u16 reason)
+{
+ BUG_ON(txq==NULL);
+ if (txq->idx != TXQ_INACTIVE && (txq->status & reason))
+ {
+ trace_txq_start(txq, reason);
+ txq->status &= ~reason;
+ if (!ecrnx_txq_is_stopped(txq) && ecrnx_txq_skb_ready(txq))
+ ecrnx_txq_add_to_hw_list(txq);
+ }
+}
+
+/**
+ * ecrnx_txq_stop - Stop one TX queue
+ *
+ * @txq: TX queue to stop
+ * @reason: reason why the TX queue is stopped (among ECRNX_TXQ_STOP_xxx)
+ *
+ * Stop the TX queue. It will remove the TX queue from HW queue list
+ * To be called with tx_lock hold
+ */
+void ecrnx_txq_stop(struct ecrnx_txq *txq, u16 reason)
+{
+ BUG_ON(txq==NULL);
+ if (txq->idx != TXQ_INACTIVE)
+ {
+ trace_txq_stop(txq, reason);
+ txq->status |= reason;
+ ecrnx_txq_del_from_hw_list(txq);
+ }
+}
+
+
+/**
+ * ecrnx_txq_sta_start - Start all the TX queue linked to a STA
+ *
+ * @sta: STA whose TX queues must be re-started
+ * @reason: Reason why the TX queue are restarted (among ECRNX_TXQ_STOP_xxx)
+ * @ecrnx_hw: Driver main data
+ *
+ * This function will re-start all the TX queues of the STA for the reason
+ * specified. It can be :
+ * - ECRNX_TXQ_STOP_STA_PS: the STA is no longer in power save mode
+ * - ECRNX_TXQ_STOP_VIF_PS: the VIF is in power save mode (p2p absence)
+ * - ECRNX_TXQ_STOP_CHAN: the STA's VIF is now on the current active channel
+ *
+ * Any TX queue with buffer ready and not Stopped for other reasons, will be
+ * added to the HW queue list
+ * To be called with tx_lock hold
+ */
+void ecrnx_txq_sta_start(struct ecrnx_sta *ecrnx_sta, u16 reason
+#ifdef CONFIG_ECRNX_FULLMAC
+ , struct ecrnx_hw *ecrnx_hw
+#endif
+ )
+{
+ struct ecrnx_txq *txq;
+ int tid;
+
+ trace_txq_sta_start(ecrnx_sta->sta_idx);
+ ECRNX_DBG("%s-%d:ecrnx_txq_stop,reaosn:0x%x,sta:0x%08x,sta_index:0x%08x \n", __func__, __LINE__, reason, ecrnx_sta, ecrnx_sta->sta_idx);
+ foreach_sta_txq(ecrnx_sta, txq, tid, ecrnx_hw) {
+ ecrnx_txq_start(txq, reason);
+
+ if (txq->idx != TXQ_INACTIVE && !skb_queue_empty(&txq->sk_list))
+ {
+ ecrnx_hwq_process(ecrnx_hw, txq->hwq);
+ }
+ }
+}
+
+
+/**
+ * ecrnx_stop_sta_txq - Stop all the TX queue linked to a STA
+ *
+ * @sta: STA whose TX queues must be stopped
+ * @reason: Reason why the TX queue are stopped (among ECRNX_TX_STOP_xxx)
+ * @ecrnx_hw: Driver main data
+ *
+ * This function will stop all the TX queues of the STA for the reason
+ * specified. It can be :
+ * - ECRNX_TXQ_STOP_STA_PS: the STA is in power save mode
+ * - ECRNX_TXQ_STOP_VIF_PS: the VIF is in power save mode (p2p absence)
+ * - ECRNX_TXQ_STOP_CHAN: the STA's VIF is not on the current active channel
+ *
+ * Any TX queue present in a HW queue list will be removed from this list.
+ * To be called with tx_lock hold
+ */
+void ecrnx_txq_sta_stop(struct ecrnx_sta *ecrnx_sta, u16 reason
+#ifdef CONFIG_ECRNX_FULLMAC
+ , struct ecrnx_hw *ecrnx_hw
+#endif
+ )
+{
+ struct ecrnx_txq *txq;
+ int tid;
+
+ if (!ecrnx_sta)
+ return;
+
+ trace_txq_sta_stop(ecrnx_sta->sta_idx);
+ foreach_sta_txq(ecrnx_sta, txq, tid, ecrnx_hw) {
+ ECRNX_DBG("%s-%d:stop_reaosn:0x%x,sta:0x%08x,sta_index:0x%08x ,txq:0x%p,tid:%d \n", __func__, __LINE__, reason, ecrnx_sta, ecrnx_sta->sta_idx, txq, tid);
+ ecrnx_txq_stop(txq, reason);
+ }
+}
+
+#ifdef CONFIG_ECRNX_SOFTMAC
+void ecrnx_txq_tdls_sta_start(struct ecrnx_sta *ecrnx_sta, u16 reason,
+ struct ecrnx_hw *ecrnx_hw)
+#else
+void ecrnx_txq_tdls_sta_start(struct ecrnx_vif *ecrnx_vif, u16 reason,
+ struct ecrnx_hw *ecrnx_hw)
+#endif
+{
+#ifdef CONFIG_ECRNX_SOFTMAC
+ trace_txq_vif_start(ecrnx_sta->vif_idx);
+ spin_lock_bh(&ecrnx_hw->tx_lock);
+
+ if (ecrnx_sta->tdls.active) {
+ ecrnx_txq_sta_start(ecrnx_sta, reason);
+ }
+
+ spin_unlock_bh(&ecrnx_hw->tx_lock);
+#else
+ trace_txq_vif_start(ecrnx_vif->vif_index);
+ spin_lock_bh(&ecrnx_hw->tx_lock);
+
+ if (ecrnx_vif->sta.tdls_sta)
+ ecrnx_txq_sta_start(ecrnx_vif->sta.tdls_sta, reason, ecrnx_hw);
+
+ spin_unlock_bh(&ecrnx_hw->tx_lock);
+#endif
+}
+
+#ifdef CONFIG_ECRNX_SOFTMAC
+void ecrnx_txq_tdls_sta_stop(struct ecrnx_sta *ecrnx_sta, u16 reason,
+ struct ecrnx_hw *ecrnx_hw)
+#else
+void ecrnx_txq_tdls_sta_stop(struct ecrnx_vif *ecrnx_vif, u16 reason,
+ struct ecrnx_hw *ecrnx_hw)
+#endif
+{
+#ifdef CONFIG_ECRNX_SOFTMAC
+ trace_txq_vif_stop(ecrnx_sta->vif_idx);
+
+ spin_lock_bh(&ecrnx_hw->tx_lock);
+
+ if (ecrnx_sta->tdls.active) {
+ ecrnx_txq_sta_stop(ecrnx_sta, reason);
+ }
+
+ spin_unlock_bh(&ecrnx_hw->tx_lock);
+#else
+ trace_txq_vif_stop(ecrnx_vif->vif_index);
+
+ spin_lock_bh(&ecrnx_hw->tx_lock);
+
+ if (ecrnx_vif->sta.tdls_sta)
+ ecrnx_txq_sta_stop(ecrnx_vif->sta.tdls_sta, reason, ecrnx_hw);
+
+ spin_unlock_bh(&ecrnx_hw->tx_lock);
+#endif
+}
+
+#ifdef CONFIG_ECRNX_FULLMAC
+static inline
+void ecrnx_txq_vif_for_each_sta(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *ecrnx_vif,
+ void (*f)(struct ecrnx_sta *, u16, struct ecrnx_hw *),
+ u16 reason)
+{
+ switch (ECRNX_VIF_TYPE(ecrnx_vif)) {
+ case NL80211_IFTYPE_STATION:
+ case NL80211_IFTYPE_P2P_CLIENT:
+ {
+ if (ecrnx_vif->tdls_status == TDLS_LINK_ACTIVE)
+ f(ecrnx_vif->sta.tdls_sta, reason, ecrnx_hw);
+#ifdef CONFIG_ECRNX_P2P
+ if (!(ecrnx_vif->sta.ap == NULL))
+#else
+ if (!WARN_ON(ecrnx_vif->sta.ap == NULL))
+#endif
+ f(ecrnx_vif->sta.ap, reason, ecrnx_hw);
+ break;
+ }
+ case NL80211_IFTYPE_AP_VLAN:
+ {
+ ecrnx_vif = ecrnx_vif->ap_vlan.master;
+ struct ecrnx_sta *sta;
+ list_for_each_entry(sta, &ecrnx_vif->ap.sta_list, list) {
+ f(sta, reason, ecrnx_hw);
+ }
+ break;
+ }
+ case NL80211_IFTYPE_AP:
+ case NL80211_IFTYPE_MESH_POINT:
+ case NL80211_IFTYPE_P2P_GO:
+ {
+ struct ecrnx_sta *sta;
+ list_for_each_entry(sta, &ecrnx_vif->ap.sta_list, list) {
+ f(sta, reason, ecrnx_hw);
+ }
+ break;
+ }
+ default:
+ BUG();
+ break;
+ }
+}
+
+#endif
+
+/**
+ * ecrnx_txq_vif_start - START TX queues of all STA associated to the vif
+ * and vif's TXQ
+ *
+ * @vif: Interface to start
+ * @reason: Start reason (ECRNX_TXQ_STOP_CHAN or ECRNX_TXQ_STOP_VIF_PS)
+ * @ecrnx_hw: Driver main data
+ *
+ * Iterate over all the STA associated to the vif and re-start them for the
+ * reason @reason
+ * Take tx_lock
+ */
+void ecrnx_txq_vif_start(struct ecrnx_vif *ecrnx_vif, u16 reason,
+ struct ecrnx_hw *ecrnx_hw)
+{
+ struct ecrnx_txq *txq;
+#ifdef CONFIG_ECRNX_SOFTMAC
+ struct ecrnx_sta *ecrnx_sta;
+ int ac;
+#endif
+
+ trace_txq_vif_start(ecrnx_vif->vif_index);
+
+ spin_lock_bh(&ecrnx_hw->tx_lock);
+
+#ifdef CONFIG_ECRNX_SOFTMAC
+ list_for_each_entry(ecrnx_sta, &ecrnx_vif->stations, list) {
+ if ((!ecrnx_vif->roc_tdls) ||
+ (ecrnx_sta->tdls.active && ecrnx_vif->roc_tdls && ecrnx_sta->tdls.chsw_en))
+ ecrnx_txq_sta_start(ecrnx_sta, reason);
+ }
+
+ foreach_vif_txq(ecrnx_vif, txq, ac) {
+ if (txq)
+ ecrnx_txq_start(txq, reason);
+ }
+#else
+ //Reject if monitor interface
+ if (ecrnx_vif->wdev.iftype == NL80211_IFTYPE_MONITOR)
+ goto end;
+
+ if (ecrnx_vif->roc_tdls && ecrnx_vif->sta.tdls_sta && ecrnx_vif->sta.tdls_sta->tdls.chsw_en) {
+ ecrnx_txq_sta_start(ecrnx_vif->sta.tdls_sta, reason, ecrnx_hw);
+ }
+ if (!ecrnx_vif->roc_tdls) {
+ ecrnx_txq_vif_for_each_sta(ecrnx_hw, ecrnx_vif, ecrnx_txq_sta_start, reason);
+ }
+
+ txq = ecrnx_txq_vif_get(ecrnx_vif, NX_BCMC_TXQ_TYPE);
+ ecrnx_txq_start(txq, reason);
+ txq = ecrnx_txq_vif_get(ecrnx_vif, NX_UNK_TXQ_TYPE);
+ ecrnx_txq_start(txq, reason);
+
+end:
+#endif /* CONFIG_ECRNX_SOFTMAC */
+
+ spin_unlock_bh(&ecrnx_hw->tx_lock);
+}
+
+
+/**
+ * ecrnx_txq_vif_stop - STOP TX queues of all STA associated to the vif
+ *
+ * @vif: Interface to stop
+ * @arg: Stop reason (ECRNX_TXQ_STOP_CHAN or ECRNX_TXQ_STOP_VIF_PS)
+ * @ecrnx_hw: Driver main data
+ *
+ * Iterate over all the STA associated to the vif and stop them for the
+ * reason ECRNX_TXQ_STOP_CHAN or ECRNX_TXQ_STOP_VIF_PS
+ * Take tx_lock
+ */
+void ecrnx_txq_vif_stop(struct ecrnx_vif *ecrnx_vif, u16 reason,
+ struct ecrnx_hw *ecrnx_hw)
+{
+ struct ecrnx_txq *txq;
+#ifdef CONFIG_ECRNX_SOFTMAC
+ struct ecrnx_sta *sta;
+ int ac;
+#endif
+
+ trace_txq_vif_stop(ecrnx_vif->vif_index);
+ spin_lock_bh(&ecrnx_hw->tx_lock);
+
+#ifdef CONFIG_ECRNX_SOFTMAC
+ list_for_each_entry(sta, &ecrnx_vif->stations, list) {
+ ecrnx_txq_sta_stop(sta, reason);
+ }
+
+ foreach_vif_txq(ecrnx_vif, txq, ac) {
+ if (txq)
+ {
+ ecrnx_txq_stop(txq, reason);
+ }
+ }
+
+#else
+ //Reject if monitor interface
+ if (ecrnx_vif->wdev.iftype == NL80211_IFTYPE_MONITOR)
+ goto end;
+
+ ecrnx_txq_vif_for_each_sta(ecrnx_hw, ecrnx_vif, ecrnx_txq_sta_stop, reason);
+
+ txq = ecrnx_txq_vif_get(ecrnx_vif, NX_BCMC_TXQ_TYPE);
+ ecrnx_txq_stop(txq, reason);
+ txq = ecrnx_txq_vif_get(ecrnx_vif, NX_UNK_TXQ_TYPE);
+ ecrnx_txq_stop(txq, reason);
+
+end:
+#endif /* CONFIG_ECRNX_SOFTMAC*/
+
+ spin_unlock_bh(&ecrnx_hw->tx_lock);
+}
+
+#ifdef CONFIG_ECRNX_FULLMAC
+
+/**
+ * ecrnx_start_offchan_txq - START TX queue for offchannel frame
+ *
+ * @ecrnx_hw: Driver main data
+ */
+void ecrnx_txq_offchan_start(struct ecrnx_hw *ecrnx_hw)
+{
+ struct ecrnx_txq *txq;
+
+ txq = &ecrnx_hw->txq[NX_OFF_CHAN_TXQ_IDX];
+ spin_lock_bh(&ecrnx_hw->tx_lock);
+ ecrnx_txq_start(txq, ECRNX_TXQ_STOP_CHAN);
+ spin_unlock_bh(&ecrnx_hw->tx_lock);
+ ECRNX_DBG("%s-%d:ecrnx_txq_start,reaosn:0x%x \n", __func__, __LINE__, ECRNX_TXQ_STOP_CHAN);
+}
+
+/**
+ * ecrnx_switch_vif_sta_txq - Associate TXQ linked to a STA to a new vif
+ *
+ * @sta: STA whose txq must be switched
+ * @old_vif: Vif currently associated to the STA (may no longer be active)
+ * @new_vif: vif which should be associated to the STA for now on
+ *
+ * This function will switch the vif (i.e. the netdev) associated to all STA's
+ * TXQ. This is used when AP_VLAN interface are created.
+ * If one STA is associated to an AP_vlan vif, it will be moved from the master
+ * AP vif to the AP_vlan vif.
+ * If an AP_vlan vif is removed, then STA will be moved back to mastert AP vif.
+ *
+ */
+void ecrnx_txq_sta_switch_vif(struct ecrnx_sta *sta, struct ecrnx_vif *old_vif,
+ struct ecrnx_vif *new_vif)
+{
+ struct ecrnx_hw *ecrnx_hw = new_vif->ecrnx_hw;
+ struct ecrnx_txq *txq;
+ int i;
+
+ /* start TXQ on the new interface, and update ndev field in txq */
+ if (!netif_carrier_ok(new_vif->ndev))
+ netif_carrier_on(new_vif->ndev);
+ txq = ecrnx_txq_sta_get(sta, 0, ecrnx_hw);
+ for (i = 0; i < NX_NB_TID_PER_STA; i++, txq++) {
+ txq->ndev = new_vif->ndev;
+ netif_wake_subqueue(txq->ndev, txq->ndev_idx);
+ }
+}
+#endif /* CONFIG_ECRNX_FULLMAC */
+
+/******************************************************************************
+ * TXQ queue/schedule functions
+ *****************************************************************************/
+/**
+ * ecrnx_txq_queue_skb - Queue a buffer in a TX queue
+ *
+ * @skb: Buffer to queue
+ * @txq: TX Queue in which the buffer must be added
+ * @ecrnx_hw: Driver main data
+ * @retry: Should it be queued in the retry list
+ *
+ * @return: Retrun 1 if txq has been added to hwq list, 0 otherwise
+ *
+ * Add a buffer in the buffer list of the TX queue
+ * and add this TX queue in the HW queue list if the txq is not stopped.
+ * If this is a retry packet it is added after the last retry packet or at the
+ * beginning if there is no retry packet queued.
+ *
+ * If the STA is in PS mode and this is the first packet queued for this txq
+ * update TIM.
+ *
+ * To be called with tx_lock hold
+ */
+int ecrnx_txq_queue_skb(struct sk_buff *skb, struct ecrnx_txq *txq,
+ struct ecrnx_hw *ecrnx_hw, bool retry,
+ struct sk_buff *skb_prev)
+{
+
+#ifdef CONFIG_ECRNX_FULLMAC
+ if (unlikely(txq->sta && txq->sta->ps.active)) {
+ txq->sta->ps.pkt_ready[txq->ps_id]++;
+ trace_ps_queue(txq->sta);
+
+ if (txq->sta->ps.pkt_ready[txq->ps_id] == 1) {
+ ecrnx_set_traffic_status(ecrnx_hw, txq->sta, true, txq->ps_id);
+ }
+ }
+#endif
+
+ if (!retry) {
+ /* add buffer in the sk_list */
+ if (skb_prev)
+ skb_append(skb_prev, skb, &txq->sk_list);
+ else
+ skb_queue_tail(&txq->sk_list, skb);
+#ifdef CONFIG_ECRNX_FULLMAC
+ // to update for SOFTMAC
+ ecrnx_ipc_sta_buffer(ecrnx_hw, txq->sta, txq->tid,
+ ((struct ecrnx_txhdr *)skb->data)->sw_hdr->frame_len);
+ ecrnx_txq_start_cleanup_timer(ecrnx_hw, txq->sta);
+#endif
+ } else {
+ if (txq->last_retry_skb)
+ skb_append(txq->last_retry_skb, skb, &txq->sk_list);
+ else
+ skb_queue_head(&txq->sk_list, skb);
+
+ txq->last_retry_skb = skb;
+ txq->nb_retry++;
+ }
+
+ trace_txq_queue_skb(skb, txq, retry);
+
+ /* Flowctrl corresponding netdev queue if needed */
+#ifdef CONFIG_ECRNX_FULLMAC
+ /* If too many buffer are queued for this TXQ stop netdev queue */
+ if ((txq->ndev_idx != NDEV_NO_TXQ) &&
+ (skb_queue_len(&txq->sk_list) > ECRNX_NDEV_FLOW_CTRL_STOP)) {
+ txq->status |= ECRNX_TXQ_NDEV_FLOW_CTRL;
+ netif_stop_subqueue(txq->ndev, txq->ndev_idx);
+ trace_txq_flowctrl_stop(txq);
+ }
+#else /* ! CONFIG_ECRNX_FULLMAC */
+
+ if (!retry && ++txq->hwq->len == txq->hwq->len_stop) {
+ trace_hwq_flowctrl_stop(txq->hwq->id);
+ ieee80211_stop_queue(ecrnx_hw->hw, txq->hwq->id);
+ ecrnx_hw->stats.queues_stops++;
+ }
+#endif /* CONFIG_ECRNX_FULLMAC */
+
+ //ECRNX_DBG("txq status: 0x%x \n", txq->status);
+ /* add it in the hwq list if not stopped and not yet present */
+ if (!ecrnx_txq_is_stopped(txq)) {
+ ecrnx_txq_add_to_hw_list(txq);
+ return 1;
+ }
+
+ return 0;
+}
+
+/**
+ * ecrnx_txq_confirm_any - Process buffer confirmed by fw
+ *
+ * @ecrnx_hw: Driver main data
+ * @txq: TX Queue
+ * @hwq: HW Queue
+ * @sw_txhdr: software descriptor of the confirmed packet
+ *
+ * Process a buffer returned by the fw. It doesn't check buffer status
+ * and only does systematic counter update:
+ * - hw credit
+ * - buffer pushed to fw
+ *
+ * To be called with tx_lock hold
+ */
+void ecrnx_txq_confirm_any(struct ecrnx_hw *ecrnx_hw, struct ecrnx_txq *txq,
+ struct ecrnx_hwq *hwq, struct ecrnx_sw_txhdr *sw_txhdr)
+{
+ int user = 0;
+
+#ifdef CONFIG_ECRNX_MUMIMO_TX
+ int group_id;
+
+ user = ECRNX_MUMIMO_INFO_POS_ID(sw_txhdr->desc.host.mumimo_info);
+ group_id = ECRNX_MUMIMO_INFO_GROUP_ID(sw_txhdr->desc.host.mumimo_info);
+
+ if ((txq->idx != TXQ_INACTIVE) &&
+ (txq->pkt_pushed[user] == 1) &&
+ (txq->status & ECRNX_TXQ_STOP_MU_POS)){
+ ecrnx_txq_start(txq, ECRNX_TXQ_STOP_MU_POS);
+ ECRNX_DBG("%s-%d:ecrnx_txq_start,reaosn:0x%x \n", __func__, __LINE__, ECRNX_TXQ_STOP_MU_POS);
+ }
+
+#endif /* CONFIG_ECRNX_MUMIMO_TX */
+
+ if (txq->pkt_pushed[user])
+ txq->pkt_pushed[user]--;
+
+ hwq->credits[user]++;
+ hwq->need_processing = true;
+ ecrnx_hw->stats.cfm_balance[hwq->id]--;
+}
+
+/******************************************************************************
+ * HWQ processing
+ *****************************************************************************/
+static inline
+bool ecrnx_txq_take_mu_lock(struct ecrnx_hw *ecrnx_hw)
+{
+ bool res = false;
+#ifdef CONFIG_ECRNX_MUMIMO_TX
+ if (ecrnx_hw->mod_params->mutx)
+ res = (down_trylock(&ecrnx_hw->mu.lock) == 0);
+#endif /* CONFIG_ECRNX_MUMIMO_TX */
+ return res;
+}
+
+static inline
+void ecrnx_txq_release_mu_lock(struct ecrnx_hw *ecrnx_hw)
+{
+#ifdef CONFIG_ECRNX_MUMIMO_TX
+ up(&ecrnx_hw->mu.lock);
+#endif /* CONFIG_ECRNX_MUMIMO_TX */
+}
+
+static inline
+void ecrnx_txq_set_mu_info(struct ecrnx_hw *ecrnx_hw, struct ecrnx_txq *txq,
+ int group_id, int pos)
+{
+#ifdef CONFIG_ECRNX_MUMIMO_TX
+ trace_txq_select_mu_group(txq, group_id, pos);
+ if (group_id) {
+ txq->mumimo_info = group_id | (pos << 6);
+ ecrnx_mu_set_active_group(ecrnx_hw, group_id);
+ } else
+ txq->mumimo_info = 0;
+#endif /* CONFIG_ECRNX_MUMIMO_TX */
+}
+
+static inline
+s8 ecrnx_txq_get_credits(struct ecrnx_txq *txq)
+{
+ s8 cred = txq->credits;
+ /* if destination is in PS mode, push_limit indicates the maximum
+ number of packet that can be pushed on this txq. */
+ if (txq->push_limit && (cred > txq->push_limit)) {
+ cred = txq->push_limit;
+ }
+ return cred;
+}
+
+/**
+ * skb_queue_extract - Extract buffer from skb list
+ *
+ * @list: List of skb to extract from
+ * @head: List of skb to append to
+ * @nb_elt: Number of skb to extract
+ *
+ * extract the first @nb_elt of @list and append them to @head
+ * It is assume that:
+ * - @list contains more that @nb_elt
+ * - There is no need to take @list nor @head lock to modify them
+ */
+static inline void skb_queue_extract(struct sk_buff_head *list,
+ struct sk_buff_head *head, int nb_elt)
+{
+ int i;
+ struct sk_buff *first, *last, *ptr;
+
+ first = ptr = list->next;
+ for (i = 0; i < nb_elt; i++) {
+ ptr = ptr->next;
+ }
+ last = ptr->prev;
+
+ /* unlink nb_elt in list */
+ list->qlen -= nb_elt;
+ list->next = ptr;
+ ptr->prev = (struct sk_buff *)list;
+
+ /* append nb_elt at end of head */
+ head->qlen += nb_elt;
+ last->next = (struct sk_buff *)head;
+ head->prev->next = first;
+ first->prev = head->prev;
+ head->prev = last;
+}
+
+
+#ifdef CONFIG_MAC80211_TXQ
+/**
+ * ecrnx_txq_mac80211_dequeue - Dequeue buffer from mac80211 txq and
+ * add them to push list
+ *
+ * @ecrnx_hw: Main driver data
+ * @sk_list: List of buffer to push (initialized without lock)
+ * @txq: TXQ to dequeue buffers from
+ * @max: Max number of buffer to dequeue
+ *
+ * Dequeue buffer from mac80211 txq, prepare them for transmission and chain them
+ * to the list of buffer to push.
+ *
+ * @return true if no more buffer are queued in mac80211 txq and false otherwise.
+ */
+static bool ecrnx_txq_mac80211_dequeue(struct ecrnx_hw *ecrnx_hw,
+ struct sk_buff_head *sk_list,
+ struct ecrnx_txq *txq, int max)
+{
+ struct ieee80211_txq *mac_txq;
+ struct sk_buff *skb;
+ unsigned long mac_txq_len;
+
+ if (txq->nb_ready_mac80211 == NOT_MAC80211_TXQ)
+ return true;
+
+ mac_txq = container_of((void *)txq, struct ieee80211_txq, drv_priv);
+
+ for (; max > 0; max--) {
+ skb = ecrnx_tx_dequeue_prep(ecrnx_hw, mac_txq);
+ if (skb == NULL)
+ return true;
+
+ __skb_queue_tail(sk_list, skb);
+ }
+
+ /* re-read mac80211 txq current length.
+ It is mainly for debug purpose to trace dropped packet. There is no
+ problems to have nb_ready_mac80211 != actual mac80211 txq length */
+ ieee80211_txq_get_depth(mac_txq, &mac_txq_len, NULL);
+ if (txq->nb_ready_mac80211 > mac_txq_len)
+ trace_txq_drop(txq, txq->nb_ready_mac80211 - mac_txq_len);
+ txq->nb_ready_mac80211 = mac_txq_len;
+
+ return (txq->nb_ready_mac80211 == 0);
+}
+#endif
+
+/**
+ * ecrnx_txq_get_skb_to_push - Get list of buffer to push for one txq
+ *
+ * @ecrnx_hw: main driver data
+ * @hwq: HWQ on wich buffers will be pushed
+ * @txq: TXQ to get buffers from
+ * @user: user postion to use
+ * @sk_list_push: list to update
+ *
+ *
+ * This function will returned a list of buffer to push for one txq.
+ * It will take into account the number of credit of the HWQ for this user
+ * position and TXQ (and push_limit).
+ * This allow to get a list that can be pushed without having to test for
+ * hwq/txq status after each push
+ *
+ * If a MU group has been selected for this txq, it will also update the
+ * counter for the group
+ *
+ * @return true if txq no longer have buffer ready after the ones returned.
+ * false otherwise
+ */
+static
+bool ecrnx_txq_get_skb_to_push(struct ecrnx_hw *ecrnx_hw, struct ecrnx_hwq *hwq,
+ struct ecrnx_txq *txq, int user,
+ struct sk_buff_head *sk_list_push)
+{
+ int nb_ready = skb_queue_len(&txq->sk_list);
+ int credits = min_t(int, ecrnx_txq_get_credits(txq), hwq->credits[user]);
+ bool res = false;
+
+ __skb_queue_head_init(sk_list_push);
+
+ if (credits >= nb_ready) {
+ skb_queue_splice_init(&txq->sk_list, sk_list_push);
+#ifdef CONFIG_MAC80211_TXQ
+ res = ecrnx_txq_mac80211_dequeue(ecrnx_hw, sk_list_push, txq, credits - nb_ready);
+ credits = skb_queue_len(sk_list_push);
+#else
+ res = true;
+ credits = nb_ready;
+#endif
+ } else {
+ skb_queue_extract(&txq->sk_list, sk_list_push, credits);
+
+ /* When processing PS service period (i.e. push_limit != 0), no longer
+ process this txq if the buffers extracted will complete the SP for
+ this txq */
+ if (txq->push_limit && (credits == txq->push_limit))
+ res = true;
+ }
+
+ ecrnx_mu_set_active_sta(ecrnx_hw, ecrnx_txq_2_sta(txq), credits);
+
+ return res;
+}
+
+/**
+ * ecrnx_txq_select_user - Select User queue for a txq
+ *
+ * @ecrnx_hw: main driver data
+ * @mu_lock: true is MU lock is taken
+ * @txq: TXQ to select MU group for
+ * @hwq: HWQ for the TXQ
+ * @user: Updated with user position selected
+ *
+ * @return false if it is no possible to process this txq.
+ * true otherwise
+ *
+ * This function selects the MU group to use for a TXQ.
+ * The selection is done as follow:
+ *
+ * - return immediately for STA that don't belongs to any group and select
+ * group 0 / user 0
+ *
+ * - If MU tx is disabled (by user mutx_on, or because mu group are being
+ * updated !mu_lock), select group 0 / user 0
+ *
+ * - Use the best group selected by @ecrnx_mu_group_sta_select.
+ *
+ * Each time a group is selected (except for the first case where sta
+ * doesn't belongs to a MU group), the function checks that no buffer is
+ * pending for this txq on another user position. If this is the case stop
+ * the txq (ECRNX_TXQ_STOP_MU_POS) and return false.
+ *
+ */
+static
+bool ecrnx_txq_select_user(struct ecrnx_hw *ecrnx_hw, bool mu_lock,
+ struct ecrnx_txq *txq, struct ecrnx_hwq *hwq, int *user)
+{
+ int pos = 0;
+#ifdef CONFIG_ECRNX_MUMIMO_TX
+ int id, group_id = 0;
+ struct ecrnx_sta *sta = ecrnx_txq_2_sta(txq);
+
+ /* for sta that belong to no group return immediately */
+ if (!sta || !sta->group_info.cnt)
+ goto end;
+
+ /* If MU is disabled, need to check user */
+ if (!ecrnx_hw->mod_params->mutx_on || !mu_lock)
+ goto check_user;
+
+ /* Use the "best" group selected */
+ group_id = sta->group_info.group;
+
+ if (group_id > 0)
+ pos = ecrnx_mu_group_sta_get_pos(ecrnx_hw, sta, group_id);
+
+ check_user:
+ /* check that we can push on this user position */
+#if CONFIG_USER_MAX == 2
+ id = (pos + 1) & 0x1;
+ if (txq->pkt_pushed[id]) {
+ ecrnx_txq_stop(txq, ECRNX_TXQ_STOP_MU_POS);
+ ECRNX_DBG("%s-%d:ecrnx_txq_stop,reaosn:0x%x \n", __func__, __LINE__, ECRNX_TXQ_STOP_MU_POS);
+ return false;
+ }
+
+#else
+ for (id = 0 ; id < CONFIG_USER_MAX ; id++) {
+ if (id != pos && txq->pkt_pushed[id]) {
+ ecrnx_txq_stop(txq, ECRNX_TXQ_STOP_MU_POS);
+ ECRNX_DBG("%s-%d:ecrnx_txq_stop,reaosn:0x%x \n", __func__, __LINE__, ECRNX_TXQ_STOP_MU_POS);
+ return false;
+ }
+ }
+#endif
+
+ end:
+ ecrnx_txq_set_mu_info(ecrnx_hw, txq, group_id, pos);
+#endif /* CONFIG_ECRNX_MUMIMO_TX */
+
+ *user = pos;
+ return true;
+}
+
+
+/**
+ * ecrnx_hwq_process - Process one HW queue list
+ *
+ * @ecrnx_hw: Driver main data
+ * @hw_queue: HW queue index to process
+ *
+ * The function will iterate over all the TX queues linked in this HW queue
+ * list. For each TX queue, push as many buffers as possible in the HW queue.
+ * (NB: TX queue have at least 1 buffer, otherwise it wouldn't be in the list)
+ * - If TX queue no longer have buffer, remove it from the list and check next
+ * TX queue
+ * - If TX queue no longer have credits or has a push_limit (PS mode) and it
+ * is reached , remove it from the list and check next TX queue
+ * - If HW queue is full, update list head to start with the next TX queue on
+ * next call if current TX queue already pushed "too many" pkt in a row, and
+ * return
+ *
+ * To be called when HW queue list is modified:
+ * - when a buffer is pushed on a TX queue
+ * - when new credits are received
+ * - when a STA returns from Power Save mode or receives traffic request.
+ * - when Channel context change
+ *
+ * To be called with tx_lock hold
+ */
+#define ALL_HWQ_MASK ((1 << CONFIG_USER_MAX) - 1)
+
+void ecrnx_hwq_process(struct ecrnx_hw *ecrnx_hw, struct ecrnx_hwq *hwq)
+{
+ struct ecrnx_txq *txq, *next;
+ int user, credit_map = 0;
+ bool mu_enable;
+
+ trace_process_hw_queue(hwq);
+
+ hwq->need_processing = false;
+
+ mu_enable = ecrnx_txq_take_mu_lock(ecrnx_hw);
+ if (!mu_enable)
+ credit_map = ALL_HWQ_MASK - 1;
+
+ list_for_each_entry_safe(txq, next, &hwq->list, sched_list) {
+ struct ecrnx_txhdr *txhdr = NULL;
+ struct sk_buff_head sk_list_push;
+ struct sk_buff *skb;
+ bool txq_empty;
+
+ trace_process_txq(txq);
+
+ /* sanity check for debug */
+ BUG_ON(!(txq->status & ECRNX_TXQ_IN_HWQ_LIST));
+ BUG_ON(txq->idx == TXQ_INACTIVE);
+ BUG_ON(txq->credits <= 0);
+ BUG_ON(!ecrnx_txq_skb_ready(txq));
+
+ if (!ecrnx_txq_select_user(ecrnx_hw, mu_enable, txq, hwq, &user))
+ continue;
+
+ if (!hwq->credits[user]) {
+ credit_map |= BIT(user);
+ if (credit_map == ALL_HWQ_MASK)
+ break;
+ continue;
+ }
+
+ txq_empty = ecrnx_txq_get_skb_to_push(ecrnx_hw, hwq, txq, user,
+ &sk_list_push);
+
+ while ((skb = __skb_dequeue(&sk_list_push)) != NULL) {
+ txhdr = (struct ecrnx_txhdr *)skb->data;
+ ecrnx_tx_push(ecrnx_hw, txhdr, 0);
+ }
+
+ if (txq_empty) {
+ ecrnx_txq_del_from_hw_list(txq);
+ txq->pkt_sent = 0;
+#if defined CONFIG_ECRNX_SOFTMAC && defined CONFIG_ECRNX_AMSDUS_TX
+ if (txq->amsdu_ht_len_cap)
+ ieee80211_amsdu_ctl(ecrnx_hw->hw, txq->sta, txq->tid, NULL,
+ 0, 0, false);
+#endif
+ } else if ((hwq->credits[user] == 0) &&
+ ecrnx_txq_is_scheduled(txq)) {
+ /* txq not empty,
+ - To avoid starving need to process other txq in the list
+ - For better aggregation, need to send "as many consecutive
+ pkt as possible" for he same txq
+ ==> Add counter to trigger txq switch
+ */
+ if (txq->pkt_sent > hwq->size) {
+ txq->pkt_sent = 0;
+ list_rotate_left(&hwq->list);
+ }
+ }
+
+#ifdef CONFIG_ECRNX_FULLMAC
+ /* Unable to complete PS traffic request because of hwq credit */
+ if (txq->push_limit && txq->sta) {
+ if (txq->ps_id == LEGACY_PS_ID) {
+ /* for legacy PS abort SP and wait next ps-poll */
+ txq->sta->ps.sp_cnt[txq->ps_id] -= txq->push_limit;
+ txq->push_limit = 0;
+ }
+ /* for u-apsd need to complete the SP to send EOSP frame */
+ }
+
+ /* restart netdev queue if number of queued buffer is below threshold */
+ if (unlikely(txq->status & ECRNX_TXQ_NDEV_FLOW_CTRL) &&
+ skb_queue_len(&txq->sk_list) < ECRNX_NDEV_FLOW_CTRL_RESTART) {
+ txq->status &= ~ECRNX_TXQ_NDEV_FLOW_CTRL;
+ netif_wake_subqueue(txq->ndev, txq->ndev_idx);
+ trace_txq_flowctrl_restart(txq);
+ }
+#endif /* CONFIG_ECRNX_FULLMAC */
+
+ }
+
+#ifdef CONFIG_ECRNX_SOFTMAC
+ if (hwq->len < hwq->len_start &&
+ ieee80211_queue_stopped(ecrnx_hw->hw, hwq->id)) {
+ trace_hwq_flowctrl_start(hwq->id);
+ ieee80211_wake_queue(ecrnx_hw->hw, hwq->id);
+ }
+#endif /* CONFIG_ECRNX_SOFTMAC */
+
+ if (mu_enable)
+ ecrnx_txq_release_mu_lock(ecrnx_hw);
+}
+
+/**
+ * ecrnx_hwq_process_all - Process all HW queue list
+ *
+ * @ecrnx_hw: Driver main data
+ *
+ * Loop over all HWQ, and process them if needed
+ * To be called with tx_lock hold
+ */
+void ecrnx_hwq_process_all(struct ecrnx_hw *ecrnx_hw)
+{
+ int id;
+
+ ecrnx_mu_group_sta_select(ecrnx_hw);
+
+ for (id = ARRAY_SIZE(ecrnx_hw->hwq) - 1; id >= 0 ; id--) {
+ if (ecrnx_hw->hwq[id].need_processing) {
+ ecrnx_hwq_process(ecrnx_hw, &ecrnx_hw->hwq[id]);
+ }
+ }
+}
+
+/**
+ * ecrnx_hwq_init - Initialize all hwq structures
+ *
+ * @ecrnx_hw: Driver main data
+ *
+ */
+void ecrnx_hwq_init(struct ecrnx_hw *ecrnx_hw)
+{
+ int i, j;
+
+ for (i = 0; i < ARRAY_SIZE(ecrnx_hw->hwq); i++) {
+ struct ecrnx_hwq *hwq = &ecrnx_hw->hwq[i];
+
+ for (j = 0 ; j < CONFIG_USER_MAX; j++)
+ hwq->credits[j] = nx_txdesc_cnt[i];
+ hwq->id = i;
+ hwq->size = nx_txdesc_cnt[i];
+ INIT_LIST_HEAD(&hwq->list);
+
+#ifdef CONFIG_ECRNX_SOFTMAC
+ hwq->len = 0;
+ hwq->len_stop = nx_txdesc_cnt[i] * 2;
+ hwq->len_start = hwq->len_stop / 4;
+#endif
+ }
+}