From 1316d5957be3b311b1494382172d4acb2f36ea59 Mon Sep 17 00:00:00 2001 From: Sara Sharon Date: Sun, 17 Apr 2016 16:28:18 +0300 Subject: iwlwifi: pcie: workaround HW shadow registers bug Integrated 9000 devices have a bug with shadow registers value retention. If driver writes RBD registers while MAC is asleep the values are stored in shadow registers to be copied whenever MAC wakes up. However, in 9000 devices a MAC wakeup is not triggered and when the bus powers down due to inactivity the shadow values and dirty bits are lost. Turn on the chicken-bits that cause MAC wakeup for RX-related values as well when the device is in D0. When the device is in low power mode turn the RX wakeup chicken bits off since driver is idle and this W/A is not needed. Remove previous W/A which was ineffective. Signed-off-by: Sara Sharon Signed-off-by: Luca Coelho --- drivers/net/wireless/intel/iwlwifi/pcie/trans.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers/net/wireless/intel/iwlwifi/pcie/trans.c') diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/trans.c b/drivers/net/wireless/intel/iwlwifi/pcie/trans.c index f603d7830a6b..33fd2175e046 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/trans.c +++ b/drivers/net/wireless/intel/iwlwifi/pcie/trans.c @@ -1286,6 +1286,8 @@ static void iwl_trans_pcie_d3_suspend(struct iwl_trans *trans, bool test, iwl_clear_bit(trans, CSR_GP_CNTRL, CSR_GP_CNTRL_REG_FLAG_INIT_DONE); + iwl_pcie_enable_rx_wake(trans, false); + if (reset) { /* * reset TX queues -- some of their registers reset during S3 @@ -1311,6 +1313,8 @@ static int iwl_trans_pcie_d3_resume(struct iwl_trans *trans, return 0; } + iwl_pcie_enable_rx_wake(trans, true); + /* * Also enables interrupts - none will happen as the device doesn't * know we're waking it up, only when the opmode actually tells it -- cgit v1.2.3 From 42db09c1b0378e118b804d948a5bab6194721506 Mon Sep 17 00:00:00 2001 From: Liad Kaufman Date: Mon, 2 May 2016 14:01:14 +0300 Subject: iwlwifi: mvm: support dqa queue sharing Support DQA queue sharing when no free queue exists for allocation to a STA that already exists. This means that a single queue will serve more than a single TID (although the RA will be the same for all TIDs served). We try to choose the lowest AC possible, to ensure the shared queues have the lowest possible combined AC requirements. The queue to share is chosen only from the same RA's DATA queues as follows (in descending priority): 1. An AC_BE queue 2. Same AC queue 3. Highest AC queue that is lower than new AC 4. Any existing AC (there always is at least 1 DATA queue) If any aggregations existed for any of the TIDs of the shared queue - they are stopped (the FW is notified), but no delBA is sent. Signed-off-by: Liad Kaufman Signed-off-by: Luca Coelho --- drivers/net/wireless/intel/iwlwifi/iwl-trans.h | 11 ++ drivers/net/wireless/intel/iwlwifi/mvm/mvm.h | 6 + drivers/net/wireless/intel/iwlwifi/mvm/sta.c | 173 +++++++++++++++++++-- drivers/net/wireless/intel/iwlwifi/mvm/utils.c | 18 ++- drivers/net/wireless/intel/iwlwifi/pcie/internal.h | 2 + drivers/net/wireless/intel/iwlwifi/pcie/trans.c | 2 + drivers/net/wireless/intel/iwlwifi/pcie/tx.c | 9 ++ 7 files changed, 199 insertions(+), 22 deletions(-) (limited to 'drivers/net/wireless/intel/iwlwifi/pcie/trans.c') diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-trans.h b/drivers/net/wireless/intel/iwlwifi/iwl-trans.h index a45be1db179b..57cc67f092ea 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-trans.h +++ b/drivers/net/wireless/intel/iwlwifi/iwl-trans.h @@ -583,6 +583,7 @@ struct iwl_trans_txq_scd_cfg { * configured. May sleep. * @txq_disable: de-configure a Tx queue to send AMPDUs * Must be atomic + * @txq_set_shared_mode: change Tx queue shared/unshared marking * @wait_tx_queue_empty: wait until tx queues are empty. May sleep. * @freeze_txq_timer: prevents the timer of the queue from firing until the * queue is set to awake. Must be atomic. @@ -646,6 +647,9 @@ struct iwl_trans_ops { void (*txq_disable)(struct iwl_trans *trans, int queue, bool configure_scd); + void (*txq_set_shared_mode)(struct iwl_trans *trans, u32 txq_id, + bool shared); + int (*wait_tx_queue_empty)(struct iwl_trans *trans, u32 txq_bm); void (*freeze_txq_timer)(struct iwl_trans *trans, unsigned long txqs, bool freeze); @@ -1061,6 +1065,13 @@ iwl_trans_txq_enable_cfg(struct iwl_trans *trans, int queue, u16 ssn, trans->ops->txq_enable(trans, queue, ssn, cfg, queue_wdg_timeout); } +static inline void iwl_trans_txq_set_shared_mode(struct iwl_trans *trans, + int queue, bool shared_mode) +{ + if (trans->ops->txq_set_shared_mode) + trans->ops->txq_set_shared_mode(trans, queue, shared_mode); +} + static inline void iwl_trans_txq_enable(struct iwl_trans *trans, int queue, int fifo, int sta_id, int tid, int frame_limit, u16 ssn, diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h b/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h index 3775e26b7d9d..6092af23969f 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h +++ b/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h @@ -687,6 +687,10 @@ struct iwl_mvm_baid_data { * This is the state of a queue that has been fully configured (including * SCD pointers, etc), has a specific RA/TID assigned to it, and can be * used to send traffic. + * @IWL_MVM_QUEUE_SHARED: queue is shared, or in a process of becoming shared + * This is a state in which a single queue serves more than one TID, all of + * which are not aggregated. Note that the queue is only associated to one + * RA. * @IWL_MVM_QUEUE_INACTIVE: queue is allocated but no traffic on it * This is a state of a queue that has had traffic on it, but during the * last %IWL_MVM_DQA_QUEUE_TIMEOUT time period there has been no traffic on @@ -698,6 +702,7 @@ enum iwl_mvm_queue_status { IWL_MVM_QUEUE_FREE, IWL_MVM_QUEUE_RESERVED, IWL_MVM_QUEUE_READY, + IWL_MVM_QUEUE_SHARED, IWL_MVM_QUEUE_INACTIVE, }; @@ -760,6 +765,7 @@ struct iwl_mvm { u8 hw_queue_refcount; u8 ra_sta_id; /* The RA this queue is mapped to, if exists */ bool reserved; /* Is this the TXQ reserved for a STA */ + u8 mac80211_ac; /* The mac80211 AC this queue is mapped to */ u16 tid_bitmap; /* Bitmap of the TIDs mapped to this queue */ /* Timestamp for inactivation per TID of this queue */ unsigned long last_frame_time[IWL_MAX_TID_COUNT + 1]; diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/sta.c b/drivers/net/wireless/intel/iwlwifi/mvm/sta.c index 84384a4321d9..14c06cbe861f 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/sta.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/sta.c @@ -361,6 +361,40 @@ static int iwl_mvm_invalidate_sta_queue(struct iwl_mvm *mvm, int queue, return ret; } +static int iwl_mvm_get_queue_agg_tids(struct iwl_mvm *mvm, int queue) +{ + struct ieee80211_sta *sta; + struct iwl_mvm_sta *mvmsta; + unsigned long tid_bitmap; + unsigned long agg_tids = 0; + s8 sta_id; + int tid; + + lockdep_assert_held(&mvm->mutex); + + spin_lock_bh(&mvm->queue_info_lock); + sta_id = mvm->queue_info[queue].ra_sta_id; + tid_bitmap = mvm->queue_info[queue].tid_bitmap; + spin_unlock_bh(&mvm->queue_info_lock); + + sta = rcu_dereference_protected(mvm->fw_id_to_mac_id[sta_id], + lockdep_is_held(&mvm->mutex)); + + if (WARN_ON_ONCE(IS_ERR_OR_NULL(sta))) + return -EINVAL; + + mvmsta = iwl_mvm_sta_from_mac80211(sta); + + spin_lock_bh(&mvmsta->lock); + for_each_set_bit(tid, &tid_bitmap, IWL_MAX_TID_COUNT + 1) { + if (mvmsta->tid_data[tid].state == IWL_AGG_ON) + agg_tids |= BIT(tid); + } + spin_unlock_bh(&mvmsta->lock); + + return agg_tids; +} + /* * Remove a queue from a station's resources. * Note that this only marks as free. It DOESN'T delete a BA agreement, and @@ -394,28 +428,91 @@ static int iwl_mvm_remove_sta_queue_marking(struct iwl_mvm *mvm, int queue) mvmsta = iwl_mvm_sta_from_mac80211(sta); spin_lock_bh(&mvmsta->lock); + /* Unmap MAC queues and TIDs from this queue */ for_each_set_bit(tid, &tid_bitmap, IWL_MAX_TID_COUNT + 1) { - mvmsta->tid_data[tid].txq_id = IEEE80211_INVAL_HW_QUEUE; - if (mvmsta->tid_data[tid].state == IWL_AGG_ON) disable_agg_tids |= BIT(tid); + mvmsta->tid_data[tid].txq_id = IEEE80211_INVAL_HW_QUEUE; } - mvmsta->tfd_queue_msk &= ~BIT(queue); /* Don't use this queue anymore */ + mvmsta->tfd_queue_msk &= ~BIT(queue); /* Don't use this queue anymore */ spin_unlock_bh(&mvmsta->lock); rcu_read_unlock(); - spin_lock(&mvm->queue_info_lock); + spin_lock_bh(&mvm->queue_info_lock); /* Unmap MAC queues and TIDs from this queue */ mvm->queue_info[queue].hw_queue_to_mac80211 = 0; mvm->queue_info[queue].hw_queue_refcount = 0; mvm->queue_info[queue].tid_bitmap = 0; - spin_unlock(&mvm->queue_info_lock); + spin_unlock_bh(&mvm->queue_info_lock); return disable_agg_tids; } +static int iwl_mvm_get_shared_queue(struct iwl_mvm *mvm, + unsigned long tfd_queue_mask, u8 ac) +{ + int queue = 0; + u8 ac_to_queue[IEEE80211_NUM_ACS]; + int i; + + lockdep_assert_held(&mvm->queue_info_lock); + + memset(&ac_to_queue, IEEE80211_INVAL_HW_QUEUE, sizeof(ac_to_queue)); + + /* See what ACs the existing queues for this STA have */ + for_each_set_bit(i, &tfd_queue_mask, IWL_MVM_DQA_MAX_DATA_QUEUE) { + /* Only DATA queues can be shared */ + if (i < IWL_MVM_DQA_MIN_DATA_QUEUE && + i != IWL_MVM_DQA_BSS_CLIENT_QUEUE) + continue; + + ac_to_queue[mvm->queue_info[i].mac80211_ac] = i; + } + + /* + * The queue to share is chosen only from DATA queues as follows (in + * descending priority): + * 1. An AC_BE queue + * 2. Same AC queue + * 3. Highest AC queue that is lower than new AC + * 4. Any existing AC (there always is at least 1 DATA queue) + */ + + /* Priority 1: An AC_BE queue */ + if (ac_to_queue[IEEE80211_AC_BE] != IEEE80211_INVAL_HW_QUEUE) + queue = ac_to_queue[IEEE80211_AC_BE]; + /* Priority 2: Same AC queue */ + else if (ac_to_queue[ac] != IEEE80211_INVAL_HW_QUEUE) + queue = ac_to_queue[ac]; + /* Priority 3a: If new AC is VO and VI exists - use VI */ + else if (ac == IEEE80211_AC_VO && + ac_to_queue[IEEE80211_AC_VI] != IEEE80211_INVAL_HW_QUEUE) + queue = ac_to_queue[IEEE80211_AC_VI]; + /* Priority 3b: No BE so only AC less than the new one is BK */ + else if (ac_to_queue[IEEE80211_AC_BK] != IEEE80211_INVAL_HW_QUEUE) + queue = ac_to_queue[IEEE80211_AC_BK]; + /* Priority 4a: No BE nor BK - use VI if exists */ + else if (ac_to_queue[IEEE80211_AC_VI] != IEEE80211_INVAL_HW_QUEUE) + queue = ac_to_queue[IEEE80211_AC_VI]; + /* Priority 4b: No BE, BK nor VI - use VO if exists */ + else if (ac_to_queue[IEEE80211_AC_VO] != IEEE80211_INVAL_HW_QUEUE) + queue = ac_to_queue[IEEE80211_AC_VO]; + + /* Make sure queue found (or not) is legal */ + if (!((queue >= IWL_MVM_DQA_MIN_MGMT_QUEUE && + queue <= IWL_MVM_DQA_MAX_MGMT_QUEUE) || + (queue >= IWL_MVM_DQA_MIN_DATA_QUEUE && + queue <= IWL_MVM_DQA_MAX_DATA_QUEUE) || + (queue == IWL_MVM_DQA_BSS_CLIENT_QUEUE))) { + IWL_ERR(mvm, "No DATA queues available to share\n"); + queue = -ENOSPC; + } + + return queue; +} + static int iwl_mvm_sta_alloc_queue(struct iwl_mvm *mvm, struct ieee80211_sta *sta, u8 ac, int tid, struct ieee80211_hdr *hdr) @@ -434,11 +531,17 @@ static int iwl_mvm_sta_alloc_queue(struct iwl_mvm *mvm, bool using_inactive_queue = false; unsigned long disable_agg_tids = 0; enum iwl_mvm_agg_state queue_state; + bool shared_queue = false; int ssn; + unsigned long tfd_queue_mask; int ret; lockdep_assert_held(&mvm->mutex); + spin_lock_bh(&mvmsta->lock); + tfd_queue_mask = mvmsta->tfd_queue_msk; + spin_unlock_bh(&mvmsta->lock); + spin_lock_bh(&mvm->queue_info_lock); /* @@ -487,20 +590,32 @@ static int iwl_mvm_sta_alloc_queue(struct iwl_mvm *mvm, queue, mvmsta->sta_id, tid); } + /* No free queue - we'll have to share */ + if (queue <= 0) { + queue = iwl_mvm_get_shared_queue(mvm, tfd_queue_mask, ac); + if (queue > 0) { + shared_queue = true; + mvm->queue_info[queue].status = IWL_MVM_QUEUE_SHARED; + } + } + /* * Mark TXQ as ready, even though it hasn't been fully configured yet, * to make sure no one else takes it. * This will allow avoiding re-acquiring the lock at the end of the * configuration. On error we'll mark it back as free. */ - if (queue >= 0) + if ((queue > 0) && !shared_queue) mvm->queue_info[queue].status = IWL_MVM_QUEUE_READY; spin_unlock_bh(&mvm->queue_info_lock); - /* TODO: support shared queues for same RA */ - if (queue < 0) + /* This shouldn't happen - out of queues */ + if (WARN_ON(queue <= 0)) { + IWL_ERR(mvm, "No available queues for tid %d on sta_id %d\n", + tid, cfg.sta_id); return -ENOSPC; + } /* * Actual en/disablement of aggregations is through the ADD_STA HCMD, @@ -543,8 +658,27 @@ static int iwl_mvm_sta_alloc_queue(struct iwl_mvm *mvm, } } - IWL_DEBUG_TX_QUEUES(mvm, "Allocating queue #%d to sta %d on tid %d\n", - queue, mvmsta->sta_id, tid); + IWL_DEBUG_TX_QUEUES(mvm, + "Allocating %squeue #%d to sta %d on tid %d\n", + shared_queue ? "shared " : "", queue, + mvmsta->sta_id, tid); + + if (shared_queue) { + /* Disable any open aggs on this queue */ + disable_agg_tids = iwl_mvm_get_queue_agg_tids(mvm, queue); + + if (disable_agg_tids) { + IWL_DEBUG_TX_QUEUES(mvm, "Disabling aggs on queue %d\n", + queue); + iwl_mvm_invalidate_sta_queue(mvm, queue, + disable_agg_tids, false); + } + + /* Mark queue as shared in transport */ + iwl_trans_txq_set_shared_mode(mvm->trans, queue, true); + + /* TODO: a redirection may be required - DQA phase 2 */ + } ssn = IEEE80211_SEQ_TO_SN(le16_to_cpu(hdr->seq_ctrl)); iwl_mvm_enable_txq(mvm, queue, mac_queue, ssn, &cfg, @@ -560,15 +694,20 @@ static int iwl_mvm_sta_alloc_queue(struct iwl_mvm *mvm, mvmsta->reserved_queue = IEEE80211_INVAL_HW_QUEUE; spin_unlock_bh(&mvmsta->lock); - ret = iwl_mvm_sta_send_to_fw(mvm, sta, true, STA_MODIFY_QUEUES); - if (ret) - goto out_err; + if (!shared_queue) { + ret = iwl_mvm_sta_send_to_fw(mvm, sta, true, STA_MODIFY_QUEUES); + if (ret) + goto out_err; - /* If we need to re-enable aggregations... */ - if (queue_state == IWL_AGG_ON) - ret = iwl_mvm_sta_tx_agg(mvm, sta, tid, queue, true); + /* If we need to re-enable aggregations... */ + if (queue_state == IWL_AGG_ON) { + ret = iwl_mvm_sta_tx_agg(mvm, sta, tid, queue, true); + if (ret) + goto out_err; + } + } - return ret; + return 0; out_err: iwl_mvm_disable_txq(mvm, queue, mac_queue, tid, 0); diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/utils.c b/drivers/net/wireless/intel/iwlwifi/mvm/utils.c index a0cb5ca4c9b9..2fc51e7c03b5 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/utils.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/utils.c @@ -655,15 +655,22 @@ void iwl_mvm_enable_txq(struct iwl_mvm *mvm, int queue, int mac80211_queue, } /* Update mappings and refcounts */ + if (mvm->queue_info[queue].hw_queue_refcount > 0) + enable_queue = false; + mvm->queue_info[queue].hw_queue_to_mac80211 |= BIT(mac80211_queue); mvm->queue_info[queue].hw_queue_refcount++; - if (mvm->queue_info[queue].hw_queue_refcount > 1) - enable_queue = false; - else - mvm->queue_info[queue].ra_sta_id = cfg->sta_id; mvm->queue_info[queue].tid_bitmap |= BIT(cfg->tid); mvm->queue_info[queue].ra_sta_id = cfg->sta_id; + if (enable_queue) { + if (cfg->tid != IWL_MAX_TID_COUNT) + mvm->queue_info[queue].mac80211_ac = + tid_to_mac80211_ac[cfg->tid]; + else + mvm->queue_info[queue].mac80211_ac = IEEE80211_AC_VO; + } + IWL_DEBUG_TX_QUEUES(mvm, "Enabling TXQ #%d refcount=%d (mac80211 map:0x%x)\n", queue, mvm->queue_info[queue].hw_queue_refcount, @@ -1154,7 +1161,8 @@ void iwl_mvm_inactivity_check(struct iwl_mvm *mvm) queue_tid_bitmap = mvm->queue_info[i].tid_bitmap; /* If TXQ isn't in active use anyway - nothing to do here... */ - if (mvm->queue_info[i].status != IWL_MVM_QUEUE_READY) { + if (mvm->queue_info[i].status != IWL_MVM_QUEUE_READY && + mvm->queue_info[i].status != IWL_MVM_QUEUE_SHARED) { spin_unlock_bh(&mvm->queue_info_lock); continue; } diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/internal.h b/drivers/net/wireless/intel/iwlwifi/pcie/internal.h index 482836ee94f2..8bfa9159d27d 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/internal.h +++ b/drivers/net/wireless/intel/iwlwifi/pcie/internal.h @@ -471,6 +471,8 @@ void iwl_trans_pcie_txq_enable(struct iwl_trans *trans, int queue, u16 ssn, unsigned int wdg_timeout); void iwl_trans_pcie_txq_disable(struct iwl_trans *trans, int queue, bool configure_scd); +void iwl_trans_pcie_txq_set_shared_mode(struct iwl_trans *trans, u32 txq_id, + bool shared_mode); int iwl_trans_pcie_tx(struct iwl_trans *trans, struct sk_buff *skb, struct iwl_device_cmd *dev_cmd, int txq_id); void iwl_pcie_txq_check_wrptrs(struct iwl_trans *trans); diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/trans.c b/drivers/net/wireless/intel/iwlwifi/pcie/trans.c index 33fd2175e046..3badebb0febf 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/trans.c +++ b/drivers/net/wireless/intel/iwlwifi/pcie/trans.c @@ -2745,6 +2745,8 @@ static const struct iwl_trans_ops trans_ops_pcie = { .txq_disable = iwl_trans_pcie_txq_disable, .txq_enable = iwl_trans_pcie_txq_enable, + .txq_set_shared_mode = iwl_trans_pcie_txq_set_shared_mode, + .wait_tx_queue_empty = iwl_trans_pcie_wait_txq_empty, .freeze_txq_timer = iwl_trans_pcie_freeze_txq_timer, .block_txq_ptrs = iwl_trans_pcie_block_txq_ptrs, diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/tx.c b/drivers/net/wireless/intel/iwlwifi/pcie/tx.c index d6beac9af029..8901d49aebd5 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/tx.c +++ b/drivers/net/wireless/intel/iwlwifi/pcie/tx.c @@ -1354,6 +1354,15 @@ void iwl_trans_pcie_txq_enable(struct iwl_trans *trans, int txq_id, u16 ssn, txq->active = true; } +void iwl_trans_pcie_txq_set_shared_mode(struct iwl_trans *trans, u32 txq_id, + bool shared_mode) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_txq *txq = &trans_pcie->txq[txq_id]; + + txq->ampdu = !shared_mode; +} + void iwl_trans_pcie_txq_disable(struct iwl_trans *trans, int txq_id, bool configure_scd) { -- cgit v1.2.3 From 2aabdbdc17b7c53490337bfc58de3409c84d85d2 Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Wed, 8 Jun 2016 23:07:31 +0300 Subject: iwlwifi: pcie: enable interrupts before releasing the NIC's CPU The NIC's CPU gets started after the firmware has been written to its memory. The first thing it does is to send an interrupt to let the driver know that it is running. In order to get that interrupt, the driver needs to make sure it is not masked. Of course, the interrupt needs to be enabled in the driver before the CPU starts to run. I mistakenly inversed those two steps leading to races which prevented the driver from getting the alive interrupt from the firmware. Fix that. Cc: [4.5+] Fixes: a6bd005fe92 ("iwlwifi: pcie: fix RF-Kill vs. firmware load race") Signed-off-by: Emmanuel Grumbach Signed-off-by: Luca Coelho --- drivers/net/wireless/intel/iwlwifi/pcie/trans.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'drivers/net/wireless/intel/iwlwifi/pcie/trans.c') diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/trans.c b/drivers/net/wireless/intel/iwlwifi/pcie/trans.c index 3badebb0febf..ac623c3e3f24 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/trans.c +++ b/drivers/net/wireless/intel/iwlwifi/pcie/trans.c @@ -801,6 +801,8 @@ static int iwl_pcie_load_cpu_sections_8000(struct iwl_trans *trans, *first_ucode_section = last_read_idx; + iwl_enable_interrupts(trans); + if (cpu == 1) iwl_write_direct32(trans, FH_UCODE_LOAD_STATUS, 0xFFFF); else @@ -980,6 +982,8 @@ static int iwl_pcie_load_given_ucode(struct iwl_trans *trans, iwl_pcie_apply_destination(trans); } + iwl_enable_interrupts(trans); + /* release CPU reset */ iwl_write32(trans, CSR_RESET, 0); @@ -1215,7 +1219,6 @@ static int iwl_trans_pcie_start_fw(struct iwl_trans *trans, ret = iwl_pcie_load_given_ucode_8000(trans, fw); else ret = iwl_pcie_load_given_ucode(trans, fw); - iwl_enable_interrupts(trans); /* re-check RF-Kill state since we may have missed the interrupt */ hw_rfkill = iwl_is_rfkill_set(trans); -- cgit v1.2.3 From 54f315cb082bf98290575e858d1e0274bb0eebe0 Mon Sep 17 00:00:00 2001 From: Ido Yariv Date: Tue, 14 Jun 2016 10:27:57 -0400 Subject: iwlwifi: pcie: Enable MSI mode when using MSI interrupts On some of the chipsets MSI & INTA interrupts are disabled by default in the HW registers, and need to be explicitly enabled to be used. In case MSI-X isn't used, make sure MSI mode is enabled by setting the relevant HW register. Signed-off-by: Ido Yariv Signed-off-by: Luca Coelho --- drivers/net/wireless/intel/iwlwifi/iwl-prph.h | 1 + drivers/net/wireless/intel/iwlwifi/pcie/trans.c | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) (limited to 'drivers/net/wireless/intel/iwlwifi/pcie/trans.c') diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-prph.h b/drivers/net/wireless/intel/iwlwifi/iwl-prph.h index 6c1d20ded04b..459bf736fd5b 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-prph.h +++ b/drivers/net/wireless/intel/iwlwifi/iwl-prph.h @@ -417,5 +417,6 @@ enum { }; #define UREG_CHICK (0xA05C00) +#define UREG_CHICK_MSI_ENABLE BIT(24) #define UREG_CHICK_MSIX_ENABLE BIT(25) #endif /* __iwl_prph_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/trans.c b/drivers/net/wireless/intel/iwlwifi/pcie/trans.c index ac623c3e3f24..f5ace924e76a 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/trans.c +++ b/drivers/net/wireless/intel/iwlwifi/pcie/trans.c @@ -1396,8 +1396,12 @@ static void iwl_pcie_init_msix(struct iwl_trans_pcie *trans_pcie) max_rx_vector = trans_pcie->allocated_vector - 1; - if (!trans_pcie->msix_enabled) + if (!trans_pcie->msix_enabled) { + if (trans->cfg->mq_rx_supported) + iwl_write_prph(trans, UREG_CHICK, + UREG_CHICK_MSI_ENABLE); return; + } iwl_write_prph(trans, UREG_CHICK, UREG_CHICK_MSIX_ENABLE); -- cgit v1.2.3 From 21cb3222fe569bd09a42771ffc8da9fb4666bd8a Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 21 Jun 2016 13:11:48 +0200 Subject: iwlwifi: decouple PCIe transport from mac80211 The PCIe transport needs to store two pointers in each TX SKB, and currently assumes mac80211's ieee80211_tx_info is present in the CB to do that. In order to remove that assumption, have the opmodes pass in the offset to where the pointers can be stored in the CB and use the offset in the PCIe code. To make the disentanglement complete, remove mac80211.h includes from everywhere in the generic iwlwifi code. This required adding an include of cfg80211.h in one place. Signed-off-by: Johannes Berg Signed-off-by: Luca Coelho --- drivers/net/wireless/intel/iwlwifi/dvm/main.c | 2 ++ .../net/wireless/intel/iwlwifi/iwl-eeprom-parse.h | 1 + drivers/net/wireless/intel/iwlwifi/iwl-modparams.h | 1 - drivers/net/wireless/intel/iwlwifi/iwl-trans.h | 9 +++-- drivers/net/wireless/intel/iwlwifi/mvm/ops.c | 3 ++ drivers/net/wireless/intel/iwlwifi/pcie/internal.h | 2 ++ drivers/net/wireless/intel/iwlwifi/pcie/trans.c | 3 ++ drivers/net/wireless/intel/iwlwifi/pcie/tx.c | 41 ++++++++++++---------- 8 files changed, 37 insertions(+), 25 deletions(-) (limited to 'drivers/net/wireless/intel/iwlwifi/pcie/trans.c') diff --git a/drivers/net/wireless/intel/iwlwifi/dvm/main.c b/drivers/net/wireless/intel/iwlwifi/dvm/main.c index 591591418316..b49848683587 100644 --- a/drivers/net/wireless/intel/iwlwifi/dvm/main.c +++ b/drivers/net/wireless/intel/iwlwifi/dvm/main.c @@ -1337,6 +1337,8 @@ static struct iwl_op_mode *iwl_op_mode_dvm_start(struct iwl_trans *trans, trans_cfg.command_groups_size = ARRAY_SIZE(iwl_dvm_groups); trans_cfg.cmd_fifo = IWLAGN_CMD_FIFO_NUM; + trans_cfg.cb_data_offs = offsetof(struct ieee80211_tx_info, + driver_data[2]); WARN_ON(sizeof(priv->transport_queue_stop) * BITS_PER_BYTE < priv->cfg->base_params->num_of_queues); diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-eeprom-parse.h b/drivers/net/wireless/intel/iwlwifi/iwl-eeprom-parse.h index 1f4e50289c14..e04a91d70a15 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-eeprom-parse.h +++ b/drivers/net/wireless/intel/iwlwifi/iwl-eeprom-parse.h @@ -66,6 +66,7 @@ #include #include +#include #include "iwl-trans.h" struct iwl_nvm_data { diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-modparams.h b/drivers/net/wireless/intel/iwlwifi/iwl-modparams.h index 0379899dc847..4d32b10fe50c 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-modparams.h +++ b/drivers/net/wireless/intel/iwlwifi/iwl-modparams.h @@ -66,7 +66,6 @@ #include #include #include -#include extern struct iwl_mod_params iwlwifi_mod_params; diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-trans.h b/drivers/net/wireless/intel/iwlwifi/iwl-trans.h index 57cc67f092ea..9ac47e02ae69 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-trans.h +++ b/drivers/net/wireless/intel/iwlwifi/iwl-trans.h @@ -392,11 +392,6 @@ static inline void iwl_free_rxb(struct iwl_rx_cmd_buffer *r) #define MAX_NO_RECLAIM_CMDS 6 -/* - * The first entry in driver_data array in ieee80211_tx_info - * that can be used by the transport. - */ -#define IWL_TRANS_FIRST_DRIVER_DATA 2 #define IWL_MASK(lo, hi) ((1 << (hi)) | ((1 << (hi)) - (1 << (lo)))) /* @@ -500,6 +495,8 @@ struct iwl_hcmd_arr { * @command_groups_size: number of command groups, to avoid illegal access * @sdio_adma_addr: the default address to set for the ADMA in SDIO mode until * we get the ALIVE from the uCode + * @cb_data_offs: offset inside skb->cb to store transport data at, must have + * space for at least two pointers */ struct iwl_trans_config { struct iwl_op_mode *op_mode; @@ -519,6 +516,8 @@ struct iwl_trans_config { int command_groups_size; u32 sdio_adma_addr; + + u8 cb_data_offs; }; struct iwl_trans_dump_data { diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/ops.c b/drivers/net/wireless/intel/iwlwifi/mvm/ops.c index a08db009d32a..55d9096da68c 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/ops.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/ops.c @@ -668,6 +668,9 @@ iwl_op_mode_mvm_start(struct iwl_trans *trans, const struct iwl_cfg *cfg, trans_cfg.cmd_fifo = IWL_MVM_TX_FIFO_CMD; trans_cfg.scd_set_active = true; + trans_cfg.cb_data_offs = offsetof(struct ieee80211_tx_info, + driver_data[2]); + trans_cfg.sdio_adma_addr = fw->sdio_adma_addr; trans_cfg.sw_csum_tx = IWL_MVM_SW_TX_CSUM_OFFLOAD; diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/internal.h b/drivers/net/wireless/intel/iwlwifi/pcie/internal.h index 8bfa9159d27d..f684b9d21912 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/internal.h +++ b/drivers/net/wireless/intel/iwlwifi/pcie/internal.h @@ -385,6 +385,8 @@ struct iwl_trans_pcie { wait_queue_head_t wait_command_queue; wait_queue_head_t d0i3_waitq; + u8 page_offs, dev_cmd_offs; + u8 cmd_queue; u8 cmd_fifo; unsigned int cmd_q_wdg_timeout; diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/trans.c b/drivers/net/wireless/intel/iwlwifi/pcie/trans.c index f5ace924e76a..3b7a4146693d 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/trans.c +++ b/drivers/net/wireless/intel/iwlwifi/pcie/trans.c @@ -1650,6 +1650,9 @@ static void iwl_trans_pcie_configure(struct iwl_trans *trans, trans_pcie->scd_set_active = trans_cfg->scd_set_active; trans_pcie->sw_csum_tx = trans_cfg->sw_csum_tx; + trans_pcie->page_offs = trans_cfg->cb_data_offs; + trans_pcie->dev_cmd_offs = trans_cfg->cb_data_offs + sizeof(void *); + trans->command_groups = trans_cfg->command_groups; trans->command_groups_size = trans_cfg->command_groups_size; diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/tx.c b/drivers/net/wireless/intel/iwlwifi/pcie/tx.c index 9b5858bc8ee2..2b688260a6a9 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/tx.c +++ b/drivers/net/wireless/intel/iwlwifi/pcie/tx.c @@ -584,16 +584,16 @@ static int iwl_pcie_txq_init(struct iwl_trans *trans, struct iwl_txq *txq, return 0; } -static void iwl_pcie_free_tso_page(struct sk_buff *skb) +static void iwl_pcie_free_tso_page(struct iwl_trans_pcie *trans_pcie, + struct sk_buff *skb) { - struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct page **page_ptr; - if (info->driver_data[IWL_TRANS_FIRST_DRIVER_DATA]) { - struct page *page = - info->driver_data[IWL_TRANS_FIRST_DRIVER_DATA]; + page_ptr = (void *)((u8 *)skb->cb + trans_pcie->page_offs); - __free_page(page); - info->driver_data[IWL_TRANS_FIRST_DRIVER_DATA] = NULL; + if (*page_ptr) { + __free_page(*page_ptr); + *page_ptr = NULL; } } @@ -639,7 +639,7 @@ static void iwl_pcie_txq_unmap(struct iwl_trans *trans, int txq_id) if (WARN_ON_ONCE(!skb)) continue; - iwl_pcie_free_tso_page(skb); + iwl_pcie_free_tso_page(trans_pcie, skb); } iwl_pcie_txq_free_tfd(trans, txq); q->read_ptr = iwl_queue_inc_wrap(q->read_ptr); @@ -1084,7 +1084,7 @@ void iwl_trans_pcie_reclaim(struct iwl_trans *trans, int txq_id, int ssn, if (WARN_ON_ONCE(!skb)) continue; - iwl_pcie_free_tso_page(skb); + iwl_pcie_free_tso_page(trans_pcie, skb); __skb_queue_tail(skbs, skb); @@ -1115,17 +1115,17 @@ void iwl_trans_pcie_reclaim(struct iwl_trans *trans, int txq_id, int ssn, while (!skb_queue_empty(&overflow_skbs)) { struct sk_buff *skb = __skb_dequeue(&overflow_skbs); - struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); - u8 dev_cmd_idx = IWL_TRANS_FIRST_DRIVER_DATA + 1; - struct iwl_device_cmd *dev_cmd = - info->driver_data[dev_cmd_idx]; + struct iwl_device_cmd *dev_cmd_ptr; + + dev_cmd_ptr = *(void **)((u8 *)skb->cb + + trans_pcie->dev_cmd_offs); /* * Note that we can very well be overflowing again. * In that case, iwl_queue_space will be small again * and we won't wake mac80211's queue. */ - iwl_trans_pcie_tx(trans, skb, dev_cmd, txq_id); + iwl_trans_pcie_tx(trans, skb, dev_cmd_ptr, txq_id); } spin_lock_bh(&txq->lock); @@ -2024,7 +2024,6 @@ static int iwl_fill_data_tbs_amsdu(struct iwl_trans *trans, struct sk_buff *skb, struct iwl_cmd_meta *out_meta, struct iwl_device_cmd *dev_cmd, u16 tb1_len) { - struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); struct iwl_trans_pcie *trans_pcie = txq->trans_pcie; struct ieee80211_hdr *hdr = (void *)skb->data; unsigned int snap_ip_tcp_hdrlen, ip_hdrlen, total_len, hdr_room; @@ -2033,6 +2032,7 @@ static int iwl_fill_data_tbs_amsdu(struct iwl_trans *trans, struct sk_buff *skb, u16 length, iv_len, amsdu_pad; u8 *start_hdr; struct iwl_tso_hdr_page *hdr_page; + struct page **page_ptr; int ret; struct tso_t tso; @@ -2063,7 +2063,8 @@ static int iwl_fill_data_tbs_amsdu(struct iwl_trans *trans, struct sk_buff *skb, get_page(hdr_page->page); start_hdr = hdr_page->pos; - info->driver_data[IWL_TRANS_FIRST_DRIVER_DATA] = hdr_page->page; + page_ptr = (void *)((u8 *)skb->cb + trans_pcie->page_offs); + *page_ptr = hdr_page->page; memcpy(hdr_page->pos, skb->data + hdr_len, iv_len); hdr_page->pos += iv_len; @@ -2273,10 +2274,12 @@ int iwl_trans_pcie_tx(struct iwl_trans *trans, struct sk_buff *skb, /* don't put the packet on the ring, if there is no room */ if (unlikely(iwl_queue_space(q) < 3)) { - struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct iwl_device_cmd **dev_cmd_ptr; + + dev_cmd_ptr = (void *)((u8 *)skb->cb + + trans_pcie->dev_cmd_offs); - info->driver_data[IWL_TRANS_FIRST_DRIVER_DATA + 1] = - dev_cmd; + *dev_cmd_ptr = dev_cmd; __skb_queue_tail(&txq->overflow_q, skb); spin_unlock(&txq->lock); -- cgit v1.2.3 From f16c3ebfa64fdf0e2dc88e6baa72da95ab70ffd7 Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Mon, 13 Jun 2016 08:28:26 +0300 Subject: iwlwifi: pcie: fix a race in firmware loading flow Upon firmware load interrupt (FH_TX), the ISR re-enables the firmware load interrupt only to avoid races with other flows as described in the commit below. When the firmware is completely loaded, the thread that is loading the firmware will enable all the interrupts to make sure that the driver gets the ALIVE interrupt. The problem with that is that the thread that is loading the firmware is actually racing against the ISR and we can get to the following situation: CPU0 CPU1 iwl_pcie_load_given_ucode ... iwl_pcie_load_firmware_chunk wait_for_interrupt ISR handles CSR_INT_BIT_FH_TX ISR wakes up the thread on CPU0 /* enable all the interrupts * to get the ALIVE interrupt */ iwl_enable_interrupts ISR re-enables CSR_INT_BIT_FH_TX only /* start the firmware */ iwl_write32(trans, CSR_RESET, 0); BUG! ALIVE interrupt will never arrive since it has been masked by CPU1. In order to fix that, change the ISR to first check if STATUS_INT_ENABLED is set. If so, re-enable all the interrupts. If STATUS_INT_ENABLED is clear, then we can check what specific interrupt happened and re-enable only that specific interrupt (RFKILL or FH_TX). All the credit for the analysis goes to Kirtika who did the actual debugging work. Cc: [4.5+] Fixes: a6bd005fe92 ("iwlwifi: pcie: fix RF-Kill vs. firmware load race") Signed-off-by: Luca Coelho --- drivers/net/wireless/intel/iwlwifi/pcie/internal.h | 21 +++++++++++++++++++-- drivers/net/wireless/intel/iwlwifi/pcie/rx.c | 16 +++++++++------- drivers/net/wireless/intel/iwlwifi/pcie/trans.c | 8 -------- 3 files changed, 28 insertions(+), 17 deletions(-) (limited to 'drivers/net/wireless/intel/iwlwifi/pcie/trans.c') diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/internal.h b/drivers/net/wireless/intel/iwlwifi/pcie/internal.h index f684b9d21912..54af3daddd24 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/internal.h +++ b/drivers/net/wireless/intel/iwlwifi/pcie/internal.h @@ -500,7 +500,7 @@ void iwl_pcie_dump_csr(struct iwl_trans *trans); /***************************************************** * Helpers ******************************************************/ -static inline void iwl_disable_interrupts(struct iwl_trans *trans) +static inline void _iwl_disable_interrupts(struct iwl_trans *trans) { struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); @@ -523,7 +523,16 @@ static inline void iwl_disable_interrupts(struct iwl_trans *trans) IWL_DEBUG_ISR(trans, "Disabled interrupts\n"); } -static inline void iwl_enable_interrupts(struct iwl_trans *trans) +static inline void iwl_disable_interrupts(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + spin_lock(&trans_pcie->irq_lock); + _iwl_disable_interrupts(trans); + spin_unlock(&trans_pcie->irq_lock); +} + +static inline void _iwl_enable_interrupts(struct iwl_trans *trans) { struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); @@ -546,6 +555,14 @@ static inline void iwl_enable_interrupts(struct iwl_trans *trans) } } +static inline void iwl_enable_interrupts(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + spin_lock(&trans_pcie->irq_lock); + _iwl_enable_interrupts(trans); + spin_unlock(&trans_pcie->irq_lock); +} static inline void iwl_enable_hw_int_msk_msix(struct iwl_trans *trans, u32 msk) { struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/rx.c b/drivers/net/wireless/intel/iwlwifi/pcie/rx.c index 0296c290f3a1..45f1b7e716a5 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/rx.c +++ b/drivers/net/wireless/intel/iwlwifi/pcie/rx.c @@ -1535,7 +1535,7 @@ irqreturn_t iwl_pcie_irq_handler(int irq, void *dev_id) * have anything to service */ if (test_bit(STATUS_INT_ENABLED, &trans->status)) - iwl_enable_interrupts(trans); + _iwl_enable_interrupts(trans); spin_unlock(&trans_pcie->irq_lock); lock_map_release(&trans->sync_cmd_lockdep_map); return IRQ_NONE; @@ -1727,15 +1727,17 @@ irqreturn_t iwl_pcie_irq_handler(int irq, void *dev_id) inta & ~trans_pcie->inta_mask); } + spin_lock(&trans_pcie->irq_lock); + /* only Re-enable all interrupt if disabled by irq */ + if (test_bit(STATUS_INT_ENABLED, &trans->status)) + _iwl_enable_interrupts(trans); /* we are loading the firmware, enable FH_TX interrupt only */ - if (handled & CSR_INT_BIT_FH_TX) + else if (handled & CSR_INT_BIT_FH_TX) iwl_enable_fw_load_int(trans); - /* only Re-enable all interrupt if disabled by irq */ - else if (test_bit(STATUS_INT_ENABLED, &trans->status)) - iwl_enable_interrupts(trans); /* Re-enable RF_KILL if it occurred */ else if (handled & CSR_INT_BIT_RF_KILL) iwl_enable_rfkill_int(trans); + spin_unlock(&trans_pcie->irq_lock); out: lock_map_release(&trans->sync_cmd_lockdep_map); @@ -1799,7 +1801,7 @@ void iwl_pcie_reset_ict(struct iwl_trans *trans) return; spin_lock(&trans_pcie->irq_lock); - iwl_disable_interrupts(trans); + _iwl_disable_interrupts(trans); memset(trans_pcie->ict_tbl, 0, ICT_SIZE); @@ -1815,7 +1817,7 @@ void iwl_pcie_reset_ict(struct iwl_trans *trans) trans_pcie->use_ict = true; trans_pcie->ict_index = 0; iwl_write32(trans, CSR_INT, trans_pcie->inta_mask); - iwl_enable_interrupts(trans); + _iwl_enable_interrupts(trans); spin_unlock(&trans_pcie->irq_lock); } diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/trans.c b/drivers/net/wireless/intel/iwlwifi/pcie/trans.c index 3b7a4146693d..9e953a4ea657 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/trans.c +++ b/drivers/net/wireless/intel/iwlwifi/pcie/trans.c @@ -1037,9 +1037,7 @@ static void _iwl_trans_pcie_stop_device(struct iwl_trans *trans, bool low_power) was_hw_rfkill = iwl_is_rfkill_set(trans); /* tell the device to stop sending interrupts */ - spin_lock(&trans_pcie->irq_lock); iwl_disable_interrupts(trans); - spin_unlock(&trans_pcie->irq_lock); /* device going down, Stop using ICT table */ iwl_pcie_disable_ict(trans); @@ -1083,9 +1081,7 @@ static void _iwl_trans_pcie_stop_device(struct iwl_trans *trans, bool low_power) * the time, unless the interrupt is ACKed even if the interrupt * should be masked. Re-ACK all the interrupts here. */ - spin_lock(&trans_pcie->irq_lock); iwl_disable_interrupts(trans); - spin_unlock(&trans_pcie->irq_lock); /* clear all status bits */ clear_bit(STATUS_SYNC_HCMD_ACTIVE, &trans->status); @@ -1578,15 +1574,11 @@ static void iwl_trans_pcie_op_mode_leave(struct iwl_trans *trans) mutex_lock(&trans_pcie->mutex); /* disable interrupts - don't enable HW RF kill interrupt */ - spin_lock(&trans_pcie->irq_lock); iwl_disable_interrupts(trans); - spin_unlock(&trans_pcie->irq_lock); iwl_pcie_apm_stop(trans, true); - spin_lock(&trans_pcie->irq_lock); iwl_disable_interrupts(trans); - spin_unlock(&trans_pcie->irq_lock); iwl_pcie_disable_ict(trans); -- cgit v1.2.3 From 564cdce735da0ad036051052e5c01760cc70494f Mon Sep 17 00:00:00 2001 From: Sara Sharon Date: Wed, 22 Jun 2016 19:25:46 +0300 Subject: iwlwifi: pcie: load FW chunk for a000 devices Update the firmware load flow for TFH hardware. Signed-off-by: Sara Sharon Signed-off-by: Luca Coelho --- drivers/net/wireless/intel/iwlwifi/iwl-fh.h | 29 +++++++++++++ drivers/net/wireless/intel/iwlwifi/pcie/trans.c | 57 ++++++++++++++++++++----- 2 files changed, 75 insertions(+), 11 deletions(-) (limited to 'drivers/net/wireless/intel/iwlwifi/pcie/trans.c') diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-fh.h b/drivers/net/wireless/intel/iwlwifi/iwl-fh.h index 3b7346797c8a..1d6f5d21a663 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-fh.h +++ b/drivers/net/wireless/intel/iwlwifi/iwl-fh.h @@ -168,6 +168,35 @@ static inline unsigned int FH_MEM_CBBC_QUEUE(struct iwl_trans *trans, * In case of a DRAM Tx CMD update the TFH will update PN and Key ID */ #define TFH_TXCMD_UPDATE_CFG (TFH_MEM_LOWER_BOUND + 0x1F48) +/* + * Controls TX DMA operation + * + * BIT fields: + * + * Bits 31:30: Enable the SRAM DMA channel. + * Turning on bit 31 will kick the SRAM2DRAM DMA. + * Note that the sram2dram may be enabled only after configuring the DRAM and + * SRAM addresses registers and the byte count register. + * Bits 25:24: Defines the interrupt target upon dram2sram transfer done. When + * set to 1 - interrupt is sent to the driver + * Bit 0: Indicates the snoop configuration +*/ +#define TFH_SRV_DMA_CHNL0_CTRL (TFH_MEM_LOWER_BOUND + 0x1F60) +#define TFH_SRV_DMA_SNOOP BIT(0) +#define TFH_SRV_DMA_TO_DRIVER BIT(24) +#define TFH_SRV_DMA_START BIT(31) + +/* Defines the DMA SRAM write start address to transfer a data block */ +#define TFH_SRV_DMA_CHNL0_SRAM_ADDR (TFH_MEM_LOWER_BOUND + 0x1F64) + +/* Defines the 64bits DRAM start address to read the DMA data block from */ +#define TFH_SRV_DMA_CHNL0_DRAM_ADDR (TFH_MEM_LOWER_BOUND + 0x1F68) + +/* + * Defines the number of bytes to transfer from DRAM to SRAM. + * Note that this register may be configured with non-dword aligned size. + */ +#define TFH_SRV_DMA_CHNL0_BC (TFH_MEM_LOWER_BOUND + 0x1F70) /** * Rx SRAM Control and Status Registers (RSCSR) diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/trans.c b/drivers/net/wireless/intel/iwlwifi/pcie/trans.c index 9e953a4ea657..af04dadb30ea 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/trans.c +++ b/drivers/net/wireless/intel/iwlwifi/pcie/trans.c @@ -608,18 +608,10 @@ static int iwl_pcie_prepare_card_hw(struct iwl_trans *trans) /* * ucode */ -static int iwl_pcie_load_firmware_chunk(struct iwl_trans *trans, u32 dst_addr, - dma_addr_t phy_addr, u32 byte_cnt) +static void iwl_pcie_load_firmware_chunk_fh(struct iwl_trans *trans, + u32 dst_addr, dma_addr_t phy_addr, + u32 byte_cnt) { - struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); - unsigned long flags; - int ret; - - trans_pcie->ucode_write_complete = false; - - if (!iwl_trans_grab_nic_access(trans, &flags)) - return -EIO; - iwl_write32(trans, FH_TCSR_CHNL_TX_CONFIG_REG(FH_SRVC_CHNL), FH_TCSR_TX_CONFIG_REG_VAL_DMA_CHNL_PAUSE); @@ -642,7 +634,50 @@ static int iwl_pcie_load_firmware_chunk(struct iwl_trans *trans, u32 dst_addr, FH_TCSR_TX_CONFIG_REG_VAL_DMA_CHNL_ENABLE | FH_TCSR_TX_CONFIG_REG_VAL_DMA_CREDIT_DISABLE | FH_TCSR_TX_CONFIG_REG_VAL_CIRQ_HOST_ENDTFD); +} + +static void iwl_pcie_load_firmware_chunk_tfh(struct iwl_trans *trans, + u32 dst_addr, dma_addr_t phy_addr, + u32 byte_cnt) +{ + /* Stop DMA channel */ + iwl_write32(trans, TFH_SRV_DMA_CHNL0_CTRL, 0); + + /* Configure SRAM address */ + iwl_write32(trans, TFH_SRV_DMA_CHNL0_SRAM_ADDR, + dst_addr); + + /* Configure DRAM address - 64 bit */ + iwl_write64(trans, TFH_SRV_DMA_CHNL0_DRAM_ADDR, phy_addr); + /* Configure byte count to transfer */ + iwl_write32(trans, TFH_SRV_DMA_CHNL0_BC, byte_cnt); + + /* Enable the DRAM2SRAM to start */ + iwl_write32(trans, TFH_SRV_DMA_CHNL0_CTRL, TFH_SRV_DMA_SNOOP | + TFH_SRV_DMA_TO_DRIVER | + TFH_SRV_DMA_START); +} + +static int iwl_pcie_load_firmware_chunk(struct iwl_trans *trans, + u32 dst_addr, dma_addr_t phy_addr, + u32 byte_cnt) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + unsigned long flags; + int ret; + + trans_pcie->ucode_write_complete = false; + + if (!iwl_trans_grab_nic_access(trans, &flags)) + return -EIO; + + if (trans->cfg->use_tfh) + iwl_pcie_load_firmware_chunk_tfh(trans, dst_addr, phy_addr, + byte_cnt); + else + iwl_pcie_load_firmware_chunk_fh(trans, dst_addr, phy_addr, + byte_cnt); iwl_trans_release_nic_access(trans, &flags); ret = wait_event_timeout(trans_pcie->ucode_write_waitq, -- cgit v1.2.3 From 38398efb74b63e03c41d13429bf7f8afefe1dbe2 Mon Sep 17 00:00:00 2001 From: Sara Sharon Date: Thu, 30 Jun 2016 11:48:30 +0300 Subject: iwlwifi: pcie: centralize SCD status logging Centralize the logging of SCD status. The motivation is that for a000 devices we will have new SCD HW, but this code was duplicate anyway, so it is a proper cleanup. Signed-off-by: Sara Sharon Signed-off-by: Luca Coelho --- drivers/net/wireless/intel/iwlwifi/pcie/internal.h | 2 + drivers/net/wireless/intel/iwlwifi/pcie/trans.c | 82 ++++++++++++---------- drivers/net/wireless/intel/iwlwifi/pcie/tx.c | 36 +--------- 3 files changed, 47 insertions(+), 73 deletions(-) (limited to 'drivers/net/wireless/intel/iwlwifi/pcie/trans.c') diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/internal.h b/drivers/net/wireless/intel/iwlwifi/pcie/internal.h index c3d89744c35e..e3047f232cb2 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/internal.h +++ b/drivers/net/wireless/intel/iwlwifi/pcie/internal.h @@ -476,6 +476,8 @@ void iwl_trans_pcie_txq_disable(struct iwl_trans *trans, int queue, bool configure_scd); void iwl_trans_pcie_txq_set_shared_mode(struct iwl_trans *trans, u32 txq_id, bool shared_mode); +void iwl_trans_pcie_log_scd_error(struct iwl_trans *trans, + struct iwl_txq *txq); int iwl_trans_pcie_tx(struct iwl_trans *trans, struct sk_buff *skb, struct iwl_device_cmd *dev_cmd, int txq_id); void iwl_pcie_txq_check_wrptrs(struct iwl_trans *trans); diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/trans.c b/drivers/net/wireless/intel/iwlwifi/pcie/trans.c index af04dadb30ea..74f2f035bd28 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/trans.c +++ b/drivers/net/wireless/intel/iwlwifi/pcie/trans.c @@ -1950,6 +1950,48 @@ static void iwl_trans_pcie_block_txq_ptrs(struct iwl_trans *trans, bool block) #define IWL_FLUSH_WAIT_MS 2000 +void iwl_trans_pcie_log_scd_error(struct iwl_trans *trans, struct iwl_txq *txq) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + u32 scd_sram_addr; + u8 buf[16]; + int cnt; + + IWL_ERR(trans, "Current SW read_ptr %d write_ptr %d\n", + txq->q.read_ptr, txq->q.write_ptr); + + scd_sram_addr = trans_pcie->scd_base_addr + + SCD_TX_STTS_QUEUE_OFFSET(txq->q.id); + iwl_trans_read_mem_bytes(trans, scd_sram_addr, buf, sizeof(buf)); + + iwl_print_hex_error(trans, buf, sizeof(buf)); + + for (cnt = 0; cnt < FH_TCSR_CHNL_NUM; cnt++) + IWL_ERR(trans, "FH TRBs(%d) = 0x%08x\n", cnt, + iwl_read_direct32(trans, FH_TX_TRB_REG(cnt))); + + for (cnt = 0; cnt < trans->cfg->base_params->num_of_queues; cnt++) { + u32 status = iwl_read_prph(trans, SCD_QUEUE_STATUS_BITS(cnt)); + u8 fifo = (status >> SCD_QUEUE_STTS_REG_POS_TXF) & 0x7; + bool active = !!(status & BIT(SCD_QUEUE_STTS_REG_POS_ACTIVE)); + u32 tbl_dw = + iwl_trans_read_mem32(trans, trans_pcie->scd_base_addr + + SCD_TRANS_TBL_OFFSET_QUEUE(cnt)); + + if (cnt & 0x1) + tbl_dw = (tbl_dw & 0xFFFF0000) >> 16; + else + tbl_dw = tbl_dw & 0x0000FFFF; + + IWL_ERR(trans, + "Q %d is %sactive and mapped to fifo %d ra_tid 0x%04x [%d,%d]\n", + cnt, active ? "" : "in", fifo, tbl_dw, + iwl_read_prph(trans, SCD_QUEUE_RDPTR(cnt)) & + (TFD_QUEUE_SIZE_MAX - 1), + iwl_read_prph(trans, SCD_QUEUE_WRPTR(cnt))); + } +} + static int iwl_trans_pcie_wait_txq_empty(struct iwl_trans *trans, u32 txq_bm) { struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); @@ -1957,8 +1999,6 @@ static int iwl_trans_pcie_wait_txq_empty(struct iwl_trans *trans, u32 txq_bm) struct iwl_queue *q; int cnt; unsigned long now = jiffies; - u32 scd_sram_addr; - u8 buf[16]; int ret = 0; /* waiting for all the tx frames complete might take a while */ @@ -1998,42 +2038,8 @@ static int iwl_trans_pcie_wait_txq_empty(struct iwl_trans *trans, u32 txq_bm) IWL_DEBUG_TX_QUEUES(trans, "Queue %d is now empty.\n", cnt); } - if (!ret) - return 0; - - IWL_ERR(trans, "Current SW read_ptr %d write_ptr %d\n", - txq->q.read_ptr, txq->q.write_ptr); - - scd_sram_addr = trans_pcie->scd_base_addr + - SCD_TX_STTS_QUEUE_OFFSET(txq->q.id); - iwl_trans_read_mem_bytes(trans, scd_sram_addr, buf, sizeof(buf)); - - iwl_print_hex_error(trans, buf, sizeof(buf)); - - for (cnt = 0; cnt < FH_TCSR_CHNL_NUM; cnt++) - IWL_ERR(trans, "FH TRBs(%d) = 0x%08x\n", cnt, - iwl_read_direct32(trans, FH_TX_TRB_REG(cnt))); - - for (cnt = 0; cnt < trans->cfg->base_params->num_of_queues; cnt++) { - u32 status = iwl_read_prph(trans, SCD_QUEUE_STATUS_BITS(cnt)); - u8 fifo = (status >> SCD_QUEUE_STTS_REG_POS_TXF) & 0x7; - bool active = !!(status & BIT(SCD_QUEUE_STTS_REG_POS_ACTIVE)); - u32 tbl_dw = - iwl_trans_read_mem32(trans, trans_pcie->scd_base_addr + - SCD_TRANS_TBL_OFFSET_QUEUE(cnt)); - - if (cnt & 0x1) - tbl_dw = (tbl_dw & 0xFFFF0000) >> 16; - else - tbl_dw = tbl_dw & 0x0000FFFF; - - IWL_ERR(trans, - "Q %d is %sactive and mapped to fifo %d ra_tid 0x%04x [%d,%d]\n", - cnt, active ? "" : "in", fifo, tbl_dw, - iwl_read_prph(trans, SCD_QUEUE_RDPTR(cnt)) & - (TFD_QUEUE_SIZE_MAX - 1), - iwl_read_prph(trans, SCD_QUEUE_WRPTR(cnt))); - } + if (ret) + iwl_trans_pcie_log_scd_error(trans, txq); return ret; } diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/tx.c b/drivers/net/wireless/intel/iwlwifi/pcie/tx.c index d88d64c92e25..18650dccdb58 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/tx.c +++ b/drivers/net/wireless/intel/iwlwifi/pcie/tx.c @@ -155,10 +155,6 @@ static void iwl_pcie_txq_stuck_timer(unsigned long data) struct iwl_txq *txq = (void *)data; struct iwl_trans_pcie *trans_pcie = txq->trans_pcie; struct iwl_trans *trans = iwl_trans_pcie_get_trans(trans_pcie); - u32 scd_sram_addr = trans_pcie->scd_base_addr + - SCD_TX_STTS_QUEUE_OFFSET(txq->q.id); - u8 buf[16]; - int i; spin_lock(&txq->lock); /* check if triggered erroneously */ @@ -170,38 +166,8 @@ static void iwl_pcie_txq_stuck_timer(unsigned long data) IWL_ERR(trans, "Queue %d stuck for %u ms.\n", txq->q.id, jiffies_to_msecs(txq->wd_timeout)); - IWL_ERR(trans, "Current SW read_ptr %d write_ptr %d\n", - txq->q.read_ptr, txq->q.write_ptr); - - iwl_trans_read_mem_bytes(trans, scd_sram_addr, buf, sizeof(buf)); - - iwl_print_hex_error(trans, buf, sizeof(buf)); - for (i = 0; i < FH_TCSR_CHNL_NUM; i++) - IWL_ERR(trans, "FH TRBs(%d) = 0x%08x\n", i, - iwl_read_direct32(trans, FH_TX_TRB_REG(i))); - - for (i = 0; i < trans->cfg->base_params->num_of_queues; i++) { - u32 status = iwl_read_prph(trans, SCD_QUEUE_STATUS_BITS(i)); - u8 fifo = (status >> SCD_QUEUE_STTS_REG_POS_TXF) & 0x7; - bool active = !!(status & BIT(SCD_QUEUE_STTS_REG_POS_ACTIVE)); - u32 tbl_dw = - iwl_trans_read_mem32(trans, - trans_pcie->scd_base_addr + - SCD_TRANS_TBL_OFFSET_QUEUE(i)); - - if (i & 0x1) - tbl_dw = (tbl_dw & 0xFFFF0000) >> 16; - else - tbl_dw = tbl_dw & 0x0000FFFF; - - IWL_ERR(trans, - "Q %d is %sactive and mapped to fifo %d ra_tid 0x%04x [%d,%d]\n", - i, active ? "" : "in", fifo, tbl_dw, - iwl_read_prph(trans, SCD_QUEUE_RDPTR(i)) & - (TFD_QUEUE_SIZE_MAX - 1), - iwl_read_prph(trans, SCD_QUEUE_WRPTR(i))); - } + iwl_trans_pcie_log_scd_error(trans, txq); iwl_force_nmi(trans); } -- cgit v1.2.3