diff options
Diffstat (limited to 'drivers/net/wireless/iwlwifi/mvm/mac80211.c')
-rw-r--r-- | drivers/net/wireless/iwlwifi/mvm/mac80211.c | 792 |
1 files changed, 687 insertions, 105 deletions
diff --git a/drivers/net/wireless/iwlwifi/mvm/mac80211.c b/drivers/net/wireless/iwlwifi/mvm/mac80211.c index 98556d0..0d6a8b7 100644 --- a/drivers/net/wireless/iwlwifi/mvm/mac80211.c +++ b/drivers/net/wireless/iwlwifi/mvm/mac80211.c @@ -80,6 +80,8 @@ #include "fw-api-scan.h" #include "iwl-phy-db.h" #include "testmode.h" +#include "iwl-fw-error-dump.h" +#include "iwl-prph.h" static const struct ieee80211_iface_limit iwl_mvm_limits[] = { { @@ -209,7 +211,9 @@ void iwl_mvm_ref(struct iwl_mvm *mvm, enum iwl_mvm_ref_type ref_type) return; IWL_DEBUG_RPM(mvm, "Take mvm reference - type %d\n", ref_type); - WARN_ON(test_and_set_bit(ref_type, mvm->ref_bitmap)); + spin_lock_bh(&mvm->refs_lock); + mvm->refs[ref_type]++; + spin_unlock_bh(&mvm->refs_lock); iwl_trans_ref(mvm->trans); } @@ -219,26 +223,47 @@ void iwl_mvm_unref(struct iwl_mvm *mvm, enum iwl_mvm_ref_type ref_type) return; IWL_DEBUG_RPM(mvm, "Leave mvm reference - type %d\n", ref_type); - WARN_ON(!test_and_clear_bit(ref_type, mvm->ref_bitmap)); + spin_lock_bh(&mvm->refs_lock); + WARN_ON(!mvm->refs[ref_type]--); + spin_unlock_bh(&mvm->refs_lock); iwl_trans_unref(mvm->trans); } -static void -iwl_mvm_unref_all_except(struct iwl_mvm *mvm, enum iwl_mvm_ref_type ref) +static void iwl_mvm_unref_all_except(struct iwl_mvm *mvm, + enum iwl_mvm_ref_type except_ref) { - int i; + int i, j; if (!iwl_mvm_is_d0i3_supported(mvm)) return; - for_each_set_bit(i, mvm->ref_bitmap, IWL_MVM_REF_COUNT) { - if (ref == i) + spin_lock_bh(&mvm->refs_lock); + for (i = 0; i < IWL_MVM_REF_COUNT; i++) { + if (except_ref == i || !mvm->refs[i]) continue; - IWL_DEBUG_RPM(mvm, "Cleanup: remove mvm ref type %d\n", i); - clear_bit(i, mvm->ref_bitmap); - iwl_trans_unref(mvm->trans); + IWL_DEBUG_RPM(mvm, "Cleanup: remove mvm ref type %d (%d)\n", + i, mvm->refs[i]); + for (j = 0; j < mvm->refs[i]; j++) + iwl_trans_unref(mvm->trans); + mvm->refs[i] = 0; + } + spin_unlock_bh(&mvm->refs_lock); +} + +int iwl_mvm_ref_sync(struct iwl_mvm *mvm, enum iwl_mvm_ref_type ref_type) +{ + iwl_mvm_ref(mvm, ref_type); + + if (!wait_event_timeout(mvm->d0i3_exit_waitq, + !test_bit(IWL_MVM_STATUS_IN_D0I3, &mvm->status), + HZ)) { + WARN_ON_ONCE(1); + iwl_mvm_unref(mvm, ref_type); + return -EIO; } + + return 0; } static void iwl_mvm_reset_phy_ctxts(struct iwl_mvm *mvm) @@ -276,6 +301,7 @@ int iwl_mvm_mac_setup_register(struct iwl_mvm *mvm) IEEE80211_HW_AMPDU_AGGREGATION | IEEE80211_HW_TIMING_BEACON_ONLY | IEEE80211_HW_CONNECTION_MONITOR | + IEEE80211_HW_CHANCTX_STA_CSA | IEEE80211_HW_SUPPORTS_DYNAMIC_SMPS | IEEE80211_HW_SUPPORTS_STATIC_SMPS; @@ -303,6 +329,9 @@ int iwl_mvm_mac_setup_register(struct iwl_mvm *mvm) hw->uapsd_max_sp_len = IWL_UAPSD_MAX_SP; } + if (mvm->fw->ucode_capa.api[0] & IWL_UCODE_TLV_API_LMAC_SCAN) + hw->flags |= IEEE80211_SINGLE_HW_SCAN_ON_ALL_BANDS; + hw->sta_data_size = sizeof(struct iwl_mvm_sta); hw->vif_data_size = sizeof(struct iwl_mvm_vif); hw->chanctx_data_size = sizeof(u16); @@ -374,6 +403,7 @@ int iwl_mvm_mac_setup_register(struct iwl_mvm *mvm) hw->wiphy->max_sched_scan_ie_len = SCAN_OFFLOAD_PROBE_REQ_SIZE - 24 - 2; hw->wiphy->features |= NL80211_FEATURE_P2P_GO_CTWIN | + NL80211_FEATURE_LOW_PRIORITY_SCAN | NL80211_FEATURE_P2P_GO_OPPPS; mvm->rts_threshold = IEEE80211_MAX_RTS_THRESHOLD; @@ -549,9 +579,6 @@ static int iwl_mvm_mac_ampdu_action(struct ieee80211_hw *hw, case IEEE80211_AMPDU_TX_STOP_FLUSH: case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT: case IEEE80211_AMPDU_TX_OPERATIONAL: - iwl_mvm_ref(mvm, IWL_MVM_REF_TX_AGG); - tx_agg_ref = true; - /* * for tx start, wait synchronously until D0i3 exit to * get the correct sequence number for the tid. @@ -560,12 +587,11 @@ static int iwl_mvm_mac_ampdu_action(struct ieee80211_hw *hw, * by the trans layer (unlike commands), so wait for * d0i3 exit in these cases as well. */ - if (!wait_event_timeout(mvm->d0i3_exit_waitq, - !test_bit(IWL_MVM_STATUS_IN_D0I3, &mvm->status), HZ)) { - WARN_ON_ONCE(1); - iwl_mvm_unref(mvm, IWL_MVM_REF_TX_AGG); - return -EIO; - } + ret = iwl_mvm_ref_sync(mvm, IWL_MVM_REF_TX_AGG); + if (ret) + return ret; + + tx_agg_ref = true; break; default: break; @@ -635,8 +661,106 @@ static void iwl_mvm_cleanup_iterator(void *data, u8 *mac, spin_unlock_bh(&mvm->time_event_lock); mvmvif->phy_ctxt = NULL; + memset(&mvmvif->bf_data, 0, sizeof(mvmvif->bf_data)); } +#ifdef CONFIG_IWLWIFI_DEBUGFS +static void iwl_mvm_fw_error_dump(struct iwl_mvm *mvm) +{ + struct iwl_fw_error_dump_file *dump_file; + struct iwl_fw_error_dump_data *dump_data; + struct iwl_fw_error_dump_info *dump_info; + struct iwl_mvm_dump_ptrs *fw_error_dump; + const struct fw_img *img; + u32 sram_len, sram_ofs; + u32 file_len, rxf_len; + unsigned long flags; + int reg_val; + + lockdep_assert_held(&mvm->mutex); + + if (mvm->fw_error_dump) + return; + + fw_error_dump = kzalloc(sizeof(*mvm->fw_error_dump), GFP_KERNEL); + if (!fw_error_dump) + return; + + img = &mvm->fw->img[mvm->cur_ucode]; + sram_ofs = img->sec[IWL_UCODE_SECTION_DATA].offset; + sram_len = img->sec[IWL_UCODE_SECTION_DATA].len; + + /* reading buffer size */ + reg_val = iwl_trans_read_prph(mvm->trans, RXF_SIZE_ADDR); + rxf_len = (reg_val & RXF_SIZE_BYTE_CNT_MSK) >> RXF_SIZE_BYTE_CND_POS; + + /* the register holds the value divided by 128 */ + rxf_len = rxf_len << 7; + + file_len = sizeof(*dump_file) + + sizeof(*dump_data) * 3 + + sram_len + + rxf_len + + sizeof(*dump_info); + + dump_file = vzalloc(file_len); + if (!dump_file) { + kfree(fw_error_dump); + return; + } + + fw_error_dump->op_mode_ptr = dump_file; + + dump_file->barker = cpu_to_le32(IWL_FW_ERROR_DUMP_BARKER); + dump_data = (void *)dump_file->data; + + dump_data->type = cpu_to_le32(IWL_FW_ERROR_DUMP_DEV_FW_INFO); + dump_data->len = cpu_to_le32(sizeof(*dump_info)); + dump_info = (void *) dump_data->data; + dump_info->device_family = + mvm->cfg->device_family == IWL_DEVICE_FAMILY_7000 ? + cpu_to_le32(IWL_FW_ERROR_DUMP_FAMILY_7) : + cpu_to_le32(IWL_FW_ERROR_DUMP_FAMILY_8); + memcpy(dump_info->fw_human_readable, mvm->fw->human_readable, + sizeof(dump_info->fw_human_readable)); + strncpy(dump_info->dev_human_readable, mvm->cfg->name, + sizeof(dump_info->dev_human_readable)); + strncpy(dump_info->bus_human_readable, mvm->dev->bus->name, + sizeof(dump_info->bus_human_readable)); + + dump_data = iwl_fw_error_next_data(dump_data); + dump_data->type = cpu_to_le32(IWL_FW_ERROR_DUMP_RXF); + dump_data->len = cpu_to_le32(rxf_len); + + if (iwl_trans_grab_nic_access(mvm->trans, false, &flags)) { + u32 *rxf = (void *)dump_data->data; + int i; + + for (i = 0; i < (rxf_len / sizeof(u32)); i++) { + iwl_trans_write_prph(mvm->trans, + RXF_LD_FENCE_OFFSET_ADDR, + i * sizeof(u32)); + rxf[i] = iwl_trans_read_prph(mvm->trans, + RXF_FIFO_RD_FENCE_ADDR); + } + iwl_trans_release_nic_access(mvm->trans, &flags); + } + + dump_data = iwl_fw_error_next_data(dump_data); + dump_data->type = cpu_to_le32(IWL_FW_ERROR_DUMP_SRAM); + dump_data->len = cpu_to_le32(sram_len); + iwl_trans_read_mem_bytes(mvm->trans, sram_ofs, dump_data->data, + sram_len); + + fw_error_dump->trans_ptr = iwl_trans_dump_data(mvm->trans); + fw_error_dump->op_mode_len = file_len; + if (fw_error_dump->trans_ptr) + file_len += fw_error_dump->trans_ptr->len; + dump_file->file_len = cpu_to_le32(file_len); + mvm->fw_error_dump = fw_error_dump; +} +#endif + static void iwl_mvm_restart_cleanup(struct iwl_mvm *mvm) { #ifdef CONFIG_IWLWIFI_DEBUGFS @@ -665,6 +789,12 @@ static void iwl_mvm_restart_cleanup(struct iwl_mvm *mvm) iwl_mvm_reset_phy_ctxts(mvm); memset(mvm->fw_key_table, 0, sizeof(mvm->fw_key_table)); memset(mvm->sta_drained, 0, sizeof(mvm->sta_drained)); + memset(&mvm->last_bt_notif, 0, sizeof(mvm->last_bt_notif)); + memset(&mvm->last_bt_notif_old, 0, sizeof(mvm->last_bt_notif_old)); + memset(&mvm->last_bt_ci_cmd, 0, sizeof(mvm->last_bt_ci_cmd)); + memset(&mvm->last_bt_ci_cmd_old, 0, sizeof(mvm->last_bt_ci_cmd_old)); + memset(&mvm->bt_ack_kill_msk, 0, sizeof(mvm->bt_ack_kill_msk)); + memset(&mvm->bt_cts_kill_msk, 0, sizeof(mvm->bt_cts_kill_msk)); ieee80211_wake_queues(mvm->hw); @@ -688,6 +818,16 @@ static int iwl_mvm_mac_start(struct ieee80211_hw *hw) iwl_mvm_restart_cleanup(mvm); ret = iwl_mvm_up(mvm); + + if (ret && test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)) { + /* Something went wrong - we need to finish some cleanup + * that normally iwl_mvm_mac_restart_complete() below + * would do. + */ + clear_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status); + iwl_mvm_d0i3_enable_tx(mvm, NULL); + } + mutex_unlock(&mvm->mutex); return ret; @@ -786,6 +926,15 @@ static int iwl_mvm_mac_add_interface(struct ieee80211_hw *hw, int ret; /* + * make sure D0i3 exit is completed, otherwise a target access + * during tx queue configuration could be done when still in + * D0i3 state. + */ + ret = iwl_mvm_ref_sync(mvm, IWL_MVM_REF_ADD_IF); + if (ret) + return ret; + + /* * Not much to do here. The stack will not allow interface * types or combinations that we didn't advertise, so we * don't really have to check the types. @@ -899,6 +1048,8 @@ static int iwl_mvm_mac_add_interface(struct ieee80211_hw *hw, out_unlock: mutex_unlock(&mvm->mutex); + iwl_mvm_unref(mvm, IWL_MVM_REF_ADD_IF); + return ret; } @@ -1255,6 +1406,28 @@ static inline int iwl_mvm_configure_bcast_filter(struct iwl_mvm *mvm, } #endif +static void iwl_mvm_teardown_tdls_peers(struct iwl_mvm *mvm) +{ + struct ieee80211_sta *sta; + struct iwl_mvm_sta *mvmsta; + int i; + + lockdep_assert_held(&mvm->mutex); + + for (i = 0; i < IWL_MVM_STATION_COUNT; i++) { + sta = rcu_dereference_protected(mvm->fw_id_to_mac_id[i], + lockdep_is_held(&mvm->mutex)); + if (!sta || IS_ERR(sta) || !sta->tdls) + continue; + + mvmsta = iwl_mvm_sta_from_mac80211(sta); + ieee80211_tdls_oper_request(mvmsta->vif, sta->addr, + NL80211_TDLS_TEARDOWN, + WLAN_REASON_TDLS_TEARDOWN_UNSPECIFIED, + GFP_KERNEL); + } +} + static void iwl_mvm_bss_info_changed_station(struct iwl_mvm *mvm, struct ieee80211_vif *vif, struct ieee80211_bss_conf *bss_conf, @@ -1278,7 +1451,7 @@ static void iwl_mvm_bss_info_changed_station(struct iwl_mvm *mvm, if (changes & BSS_CHANGED_ASSOC) { if (bss_conf->assoc) { /* add quota for this interface */ - ret = iwl_mvm_update_quotas(mvm, vif); + ret = iwl_mvm_update_quotas(mvm, NULL); if (ret) { IWL_ERR(mvm, "failed to update quotas\n"); return; @@ -1350,14 +1523,18 @@ static void iwl_mvm_bss_info_changed_station(struct iwl_mvm *mvm, */ iwl_mvm_remove_time_event(mvm, mvmvif, &mvmvif->time_event_data); - iwl_mvm_sf_update(mvm, vif, false); - WARN_ON(iwl_mvm_enable_beacon_filter(mvm, vif, 0)); } else if (changes & (BSS_CHANGED_PS | BSS_CHANGED_P2P_PS | BSS_CHANGED_QOS)) { ret = iwl_mvm_power_update_mac(mvm); if (ret) IWL_ERR(mvm, "failed to update power mode\n"); } + + if (changes & BSS_CHANGED_BEACON_INFO) { + iwl_mvm_sf_update(mvm, vif, false); + WARN_ON(iwl_mvm_enable_beacon_filter(mvm, vif, 0)); + } + if (changes & BSS_CHANGED_TXPOWER) { IWL_DEBUG_CALIB(mvm, "Changing TX Power to %d\n", bss_conf->txpower); @@ -1389,6 +1566,14 @@ static int iwl_mvm_start_ap_ibss(struct ieee80211_hw *hw, struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); int ret; + /* + * iwl_mvm_mac_ctxt_add() might read directly from the device + * (the system time), so make sure it is available. + */ + ret = iwl_mvm_ref_sync(mvm, IWL_MVM_REF_START_AP); + if (ret) + return ret; + mutex_lock(&mvm->mutex); /* Send the beacon template */ @@ -1425,7 +1610,7 @@ static int iwl_mvm_start_ap_ibss(struct ieee80211_hw *hw, /* power updated needs to be done before quotas */ iwl_mvm_power_update_mac(mvm); - ret = iwl_mvm_update_quotas(mvm, vif); + ret = iwl_mvm_update_quotas(mvm, NULL); if (ret) goto out_quota_failed; @@ -1437,6 +1622,10 @@ static int iwl_mvm_start_ap_ibss(struct ieee80211_hw *hw, iwl_mvm_bt_coex_vif_change(mvm); + /* we don't support TDLS during DCM */ + if (iwl_mvm_phy_ctx_count(mvm) > 1) + iwl_mvm_teardown_tdls_peers(mvm); + mutex_unlock(&mvm->mutex); return 0; @@ -1450,6 +1639,7 @@ out_remove: iwl_mvm_mac_ctxt_remove(mvm, vif); out_unlock: mutex_unlock(&mvm->mutex); + iwl_mvm_unref(mvm, IWL_MVM_REF_START_AP); return ret; } @@ -1463,7 +1653,20 @@ static void iwl_mvm_stop_ap_ibss(struct ieee80211_hw *hw, mutex_lock(&mvm->mutex); + /* Handle AP stop while in CSA */ + if (rcu_access_pointer(mvm->csa_vif) == vif) { + iwl_mvm_remove_time_event(mvm, mvmvif, + &mvmvif->time_event_data); + RCU_INIT_POINTER(mvm->csa_vif, NULL); + } + + if (rcu_access_pointer(mvm->csa_tx_blocked_vif) == vif) { + RCU_INIT_POINTER(mvm->csa_tx_blocked_vif, NULL); + mvm->csa_tx_block_bcn_timeout = 0; + } + mvmvif->ap_ibss_active = false; + mvm->ap_last_beacon_gp2 = 0; iwl_mvm_bt_coex_vif_change(mvm); @@ -1514,10 +1717,18 @@ static void iwl_mvm_bss_info_changed(struct ieee80211_hw *hw, { struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + /* + * iwl_mvm_bss_info_changed_station() might call + * iwl_mvm_protect_session(), which reads directly from + * the device (the system time), so make sure it is available. + */ + if (iwl_mvm_ref_sync(mvm, IWL_MVM_REF_BSS_CHANGED)) + return; + mutex_lock(&mvm->mutex); if (changes & BSS_CHANGED_IDLE && !bss_conf->idle) - iwl_mvm_sched_scan_stop(mvm, true); + iwl_mvm_scan_offload_stop(mvm, true); switch (vif->type) { case NL80211_IFTYPE_STATION: @@ -1533,44 +1744,84 @@ static void iwl_mvm_bss_info_changed(struct ieee80211_hw *hw, } mutex_unlock(&mvm->mutex); + iwl_mvm_unref(mvm, IWL_MVM_REF_BSS_CHANGED); } +static int iwl_mvm_cancel_scan_wait_notif(struct iwl_mvm *mvm, + enum iwl_scan_status scan_type) +{ + int ret; + bool wait_for_handlers = false; + + mutex_lock(&mvm->mutex); + + if (mvm->scan_status != scan_type) { + ret = 0; + /* make sure there are no pending notifications */ + wait_for_handlers = true; + goto out; + } + + switch (scan_type) { + case IWL_MVM_SCAN_SCHED: + ret = iwl_mvm_scan_offload_stop(mvm, true); + break; + case IWL_MVM_SCAN_OS: + ret = iwl_mvm_cancel_scan(mvm); + break; + case IWL_MVM_SCAN_NONE: + default: + WARN_ON_ONCE(1); + ret = -EINVAL; + break; + } + if (ret) + goto out; + + wait_for_handlers = true; +out: + mutex_unlock(&mvm->mutex); + + /* make sure we consume the completion notification */ + if (wait_for_handlers) + iwl_mvm_wait_for_async_handlers(mvm); + + return ret; +} static int iwl_mvm_mac_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif, - struct cfg80211_scan_request *req) + struct ieee80211_scan_request *hw_req) { struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct cfg80211_scan_request *req = &hw_req->req; int ret; - if (req->n_channels == 0 || req->n_channels > MAX_NUM_SCAN_CHANNELS) + if (req->n_channels == 0 || + req->n_channels > mvm->fw->ucode_capa.n_scan_channels) return -EINVAL; + ret = iwl_mvm_cancel_scan_wait_notif(mvm, IWL_MVM_SCAN_SCHED); + if (ret) + return ret; + mutex_lock(&mvm->mutex); - switch (mvm->scan_status) { - case IWL_MVM_SCAN_SCHED: - ret = iwl_mvm_sched_scan_stop(mvm, true); - if (ret) { - ret = -EBUSY; - goto out; - } - break; - case IWL_MVM_SCAN_NONE: - break; - default: + if (mvm->scan_status != IWL_MVM_SCAN_NONE) { ret = -EBUSY; goto out; } iwl_mvm_ref(mvm, IWL_MVM_REF_SCAN); - ret = iwl_mvm_scan_request(mvm, vif, req); + if (mvm->fw->ucode_capa.api[0] & IWL_UCODE_TLV_API_LMAC_SCAN) + ret = iwl_mvm_unified_scan_lmac(mvm, vif, hw_req); + else + ret = iwl_mvm_scan_request(mvm, vif, req); + if (ret) iwl_mvm_unref(mvm, IWL_MVM_REF_SCAN); out: mutex_unlock(&mvm->mutex); - /* make sure to flush the Rx handler before the next scan arrives */ - iwl_mvm_wait_for_async_handlers(mvm); return ret; } @@ -1680,6 +1931,48 @@ static void iwl_mvm_sta_pre_rcu_remove(struct ieee80211_hw *hw, mutex_unlock(&mvm->mutex); } +int iwl_mvm_tdls_sta_count(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct ieee80211_sta *sta; + struct iwl_mvm_sta *mvmsta; + int count = 0; + int i; + + lockdep_assert_held(&mvm->mutex); + + for (i = 0; i < IWL_MVM_STATION_COUNT; i++) { + sta = rcu_dereference_protected(mvm->fw_id_to_mac_id[i], + lockdep_is_held(&mvm->mutex)); + if (!sta || IS_ERR(sta) || !sta->tdls) + continue; + + if (vif) { + mvmsta = iwl_mvm_sta_from_mac80211(sta); + if (mvmsta->vif != vif) + continue; + } + + count++; + } + + return count; +} + +static void iwl_mvm_recalc_tdls_state(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + bool sta_added) +{ + int tdls_sta_cnt = iwl_mvm_tdls_sta_count(mvm, vif); + + /* + * Disable ps when the first TDLS sta is added and re-enable it + * when the last TDLS sta is removed + */ + if ((tdls_sta_cnt == 1 && sta_added) || + (tdls_sta_cnt == 0 && !sta_added)) + iwl_mvm_power_update_mac(mvm); +} + static int iwl_mvm_mac_sta_state(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_sta *sta, @@ -1718,7 +2011,20 @@ static int iwl_mvm_mac_sta_state(struct ieee80211_hw *hw, ret = -EINVAL; goto out_unlock; } + + if (sta->tdls && + (vif->p2p || + iwl_mvm_tdls_sta_count(mvm, NULL) == + IWL_MVM_TDLS_STA_COUNT || + iwl_mvm_phy_ctx_count(mvm) > 1)) { + IWL_DEBUG_MAC80211(mvm, "refusing TDLS sta\n"); + ret = -EBUSY; + goto out_unlock; + } + ret = iwl_mvm_add_sta(mvm, vif, sta); + if (sta->tdls && ret == 0) + iwl_mvm_recalc_tdls_state(mvm, vif, true); } else if (old_state == IEEE80211_STA_NONE && new_state == IEEE80211_STA_AUTH) { /* @@ -1736,6 +2042,11 @@ static int iwl_mvm_mac_sta_state(struct ieee80211_hw *hw, true); } else if (old_state == IEEE80211_STA_ASSOC && new_state == IEEE80211_STA_AUTHORIZED) { + + /* we don't support TDLS during DCM */ + if (iwl_mvm_phy_ctx_count(mvm) > 1) + iwl_mvm_teardown_tdls_peers(mvm); + /* enable beacon filtering */ WARN_ON(iwl_mvm_enable_beacon_filter(mvm, vif, 0)); ret = 0; @@ -1753,6 +2064,8 @@ static int iwl_mvm_mac_sta_state(struct ieee80211_hw *hw, } else if (old_state == IEEE80211_STA_NONE && new_state == IEEE80211_STA_NOTEXIST) { ret = iwl_mvm_rm_sta(mvm, vif, sta); + if (sta->tdls) + iwl_mvm_recalc_tdls_state(mvm, vif, false); } else { ret = -EIO; } @@ -1818,20 +2131,54 @@ static void iwl_mvm_mac_mgd_prepare_tx(struct ieee80211_hw *hw, if (WARN_ON_ONCE(vif->bss_conf.assoc)) return; + /* + * iwl_mvm_protect_session() reads directly from the device + * (the system time), so make sure it is available. + */ + if (iwl_mvm_ref_sync(mvm, IWL_MVM_REF_PREPARE_TX)) + return; + mutex_lock(&mvm->mutex); /* Try really hard to protect the session and hear a beacon */ iwl_mvm_protect_session(mvm, vif, duration, min_duration, 500); mutex_unlock(&mvm->mutex); + + iwl_mvm_unref(mvm, IWL_MVM_REF_PREPARE_TX); +} + +static void iwl_mvm_mac_mgd_protect_tdls_discover(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + u32 duration = 2 * vif->bss_conf.dtim_period * vif->bss_conf.beacon_int; + + /* + * iwl_mvm_protect_session() reads directly from the device + * (the system time), so make sure it is available. + */ + if (iwl_mvm_ref_sync(mvm, IWL_MVM_REF_PROTECT_TDLS)) + return; + + mutex_lock(&mvm->mutex); + /* Protect the session to hear the TDLS setup response on the channel */ + iwl_mvm_protect_session(mvm, vif, duration, duration, 100); + mutex_unlock(&mvm->mutex); + + iwl_mvm_unref(mvm, IWL_MVM_REF_PROTECT_TDLS); } static int iwl_mvm_mac_sched_scan_start(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct cfg80211_sched_scan_request *req, - struct ieee80211_sched_scan_ies *ies) + struct ieee80211_scan_ies *ies) { struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); int ret; + ret = iwl_mvm_cancel_scan_wait_notif(mvm, IWL_MVM_SCAN_OS); + if (ret) + return ret; + mutex_lock(&mvm->mutex); if (!iwl_mvm_is_idle(mvm)) { @@ -1839,49 +2186,34 @@ static int iwl_mvm_mac_sched_scan_start(struct ieee80211_hw *hw, goto out; } - switch (mvm->scan_status) { - case IWL_MVM_SCAN_OS: - IWL_DEBUG_SCAN(mvm, "Stopping previous scan for sched_scan\n"); - ret = iwl_mvm_cancel_scan(mvm); - if (ret) { - ret = -EBUSY; - goto out; - } - - /* - * iwl_mvm_rx_scan_complete() will be called soon but will - * not reset the scan status as it won't be IWL_MVM_SCAN_OS - * any more since we queue the next scan immediately (below). - * We make sure it is called before the next scan starts by - * flushing the async-handlers work. - */ - break; - case IWL_MVM_SCAN_NONE: - break; - default: + if (mvm->scan_status != IWL_MVM_SCAN_NONE) { ret = -EBUSY; goto out; } mvm->scan_status = IWL_MVM_SCAN_SCHED; - ret = iwl_mvm_config_sched_scan(mvm, vif, req, ies); - if (ret) - goto err; + if (!(mvm->fw->ucode_capa.api[0] & IWL_UCODE_TLV_API_LMAC_SCAN)) { + ret = iwl_mvm_config_sched_scan(mvm, vif, req, ies); + if (ret) + goto err; + } ret = iwl_mvm_config_sched_scan_profiles(mvm, req); if (ret) goto err; - ret = iwl_mvm_sched_scan_start(mvm, req); + if (mvm->fw->ucode_capa.api[0] & IWL_UCODE_TLV_API_LMAC_SCAN) + ret = iwl_mvm_unified_sched_scan_lmac(mvm, vif, req, ies); + else + ret = iwl_mvm_sched_scan_start(mvm, req); + if (!ret) goto out; err: mvm->scan_status = IWL_MVM_SCAN_NONE; out: mutex_unlock(&mvm->mutex); - /* make sure to flush the Rx handler before the next scan arrives */ - iwl_mvm_wait_for_async_handlers(mvm); return ret; } @@ -1892,7 +2224,7 @@ static int iwl_mvm_mac_sched_scan_stop(struct ieee80211_hw *hw, int ret; mutex_lock(&mvm->mutex); - ret = iwl_mvm_sched_scan_stop(mvm, false); + ret = iwl_mvm_scan_offload_stop(mvm, false); mutex_unlock(&mvm->mutex); iwl_mvm_wait_for_async_handlers(mvm); @@ -2001,6 +2333,119 @@ static void iwl_mvm_mac_update_tkip_key(struct ieee80211_hw *hw, } +static bool iwl_mvm_rx_aux_roc(struct iwl_notif_wait_data *notif_wait, + struct iwl_rx_packet *pkt, void *data) +{ + struct iwl_mvm *mvm = + container_of(notif_wait, struct iwl_mvm, notif_wait); + struct iwl_hs20_roc_res *resp; + int resp_len = iwl_rx_packet_payload_len(pkt); + struct iwl_mvm_time_event_data *te_data = data; + + if (WARN_ON(pkt->hdr.cmd != HOT_SPOT_CMD)) + return true; + + if (WARN_ON_ONCE(resp_len != sizeof(*resp))) { + IWL_ERR(mvm, "Invalid HOT_SPOT_CMD response\n"); + return true; + } + + resp = (void *)pkt->data; + + IWL_DEBUG_TE(mvm, + "Aux ROC: Recieved response from ucode: status=%d uid=%d\n", + resp->status, resp->event_unique_id); + + te_data->uid = le32_to_cpu(resp->event_unique_id); + IWL_DEBUG_TE(mvm, "TIME_EVENT_CMD response - UID = 0x%x\n", + te_data->uid); + + spin_lock_bh(&mvm->time_event_lock); + list_add_tail(&te_data->list, &mvm->aux_roc_te_list); + spin_unlock_bh(&mvm->time_event_lock); + + return true; +} + +#define AUX_ROC_MAX_DELAY_ON_CHANNEL 5000 +static int iwl_mvm_send_aux_roc_cmd(struct iwl_mvm *mvm, + struct ieee80211_channel *channel, + struct ieee80211_vif *vif, + int duration) +{ + int res, time_reg = DEVICE_SYSTEM_TIME_REG; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_time_event_data *te_data = &mvmvif->hs_time_event_data; + static const u8 time_event_response[] = { HOT_SPOT_CMD }; + struct iwl_notification_wait wait_time_event; + struct iwl_hs20_roc_req aux_roc_req = { + .action = cpu_to_le32(FW_CTXT_ACTION_ADD), + .id_and_color = + cpu_to_le32(FW_CMD_ID_AND_COLOR(MAC_INDEX_AUX, 0)), + .sta_id_and_color = cpu_to_le32(mvm->aux_sta.sta_id), + /* Set the channel info data */ + .channel_info.band = (channel->band == IEEE80211_BAND_2GHZ) ? + PHY_BAND_24 : PHY_BAND_5, + .channel_info.channel = channel->hw_value, + .channel_info.width = PHY_VHT_CHANNEL_MODE20, + /* Set the time and duration */ + .apply_time = cpu_to_le32(iwl_read_prph(mvm->trans, time_reg)), + .apply_time_max_delay = + cpu_to_le32(MSEC_TO_TU(AUX_ROC_MAX_DELAY_ON_CHANNEL)), + .duration = cpu_to_le32(MSEC_TO_TU(duration)), + }; + + /* Set the node address */ + memcpy(aux_roc_req.node_addr, vif->addr, ETH_ALEN); + + te_data->vif = vif; + te_data->duration = duration; + te_data->id = HOT_SPOT_CMD; + + lockdep_assert_held(&mvm->mutex); + + spin_lock_bh(&mvm->time_event_lock); + list_add_tail(&te_data->list, &mvm->time_event_list); + spin_unlock_bh(&mvm->time_event_lock); + + /* + * Use a notification wait, which really just processes the + * command response and doesn't wait for anything, in order + * to be able to process the response and get the UID inside + * the RX path. Using CMD_WANT_SKB doesn't work because it + * stores the buffer and then wakes up this thread, by which + * time another notification (that the time event started) + * might already be processed unsuccessfully. + */ + iwl_init_notification_wait(&mvm->notif_wait, &wait_time_event, + time_event_response, + ARRAY_SIZE(time_event_response), + iwl_mvm_rx_aux_roc, te_data); + + res = iwl_mvm_send_cmd_pdu(mvm, HOT_SPOT_CMD, 0, sizeof(aux_roc_req), + &aux_roc_req); + + if (res) { + IWL_ERR(mvm, "Couldn't send HOT_SPOT_CMD: %d\n", res); + iwl_remove_notification(&mvm->notif_wait, &wait_time_event); + goto out_clear_te; + } + + /* No need to wait for anything, so just pass 1 (0 isn't valid) */ + res = iwl_wait_notification(&mvm->notif_wait, &wait_time_event, 1); + /* should never fail */ + WARN_ON_ONCE(res); + + if (res) { + out_clear_te: + spin_lock_bh(&mvm->time_event_lock); + iwl_mvm_te_clear_data(mvm, te_data); + spin_unlock_bh(&mvm->time_event_lock); + } + + return res; +} + static int iwl_mvm_roc(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_channel *channel, @@ -2016,8 +2461,17 @@ static int iwl_mvm_roc(struct ieee80211_hw *hw, IWL_DEBUG_MAC80211(mvm, "enter (%d, %d, %d)\n", channel->hw_value, duration, type); - if (vif->type != NL80211_IFTYPE_P2P_DEVICE) { - IWL_ERR(mvm, "vif isn't a P2P_DEVICE: %d\n", vif->type); + switch (vif->type) { + case NL80211_IFTYPE_STATION: + /* Use aux roc framework (HS20) */ + ret = iwl_mvm_send_aux_roc_cmd(mvm, channel, + vif, duration); + return ret; + case NL80211_IFTYPE_P2P_DEVICE: + /* handle below */ + break; + default: + IWL_ERR(mvm, "vif isn't P2P_DEVICE: %d\n", vif->type); return -EINVAL; } @@ -2126,17 +2580,17 @@ static int iwl_mvm_cancel_roc(struct ieee80211_hw *hw) return 0; } -static int iwl_mvm_add_chanctx(struct ieee80211_hw *hw, - struct ieee80211_chanctx_conf *ctx) +static int __iwl_mvm_add_chanctx(struct iwl_mvm *mvm, + struct ieee80211_chanctx_conf *ctx) { - struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); u16 *phy_ctxt_id = (u16 *)ctx->drv_priv; struct iwl_mvm_phy_ctxt *phy_ctxt; int ret; + lockdep_assert_held(&mvm->mutex); + IWL_DEBUG_MAC80211(mvm, "Add channel context\n"); - mutex_lock(&mvm->mutex); phy_ctxt = iwl_mvm_get_free_phy_ctxt(mvm); if (!phy_ctxt) { ret = -ENOSPC; @@ -2154,19 +2608,40 @@ static int iwl_mvm_add_chanctx(struct ieee80211_hw *hw, iwl_mvm_phy_ctxt_ref(mvm, phy_ctxt); *phy_ctxt_id = phy_ctxt->id; out: + return ret; +} + +static int iwl_mvm_add_chanctx(struct ieee80211_hw *hw, + struct ieee80211_chanctx_conf *ctx) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + int ret; + + mutex_lock(&mvm->mutex); + ret = __iwl_mvm_add_chanctx(mvm, ctx); mutex_unlock(&mvm->mutex); + return ret; } +static void __iwl_mvm_remove_chanctx(struct iwl_mvm *mvm, + struct ieee80211_chanctx_conf *ctx) +{ + u16 *phy_ctxt_id = (u16 *)ctx->drv_priv; + struct iwl_mvm_phy_ctxt *phy_ctxt = &mvm->phy_ctxts[*phy_ctxt_id]; + + lockdep_assert_held(&mvm->mutex); + + iwl_mvm_phy_ctxt_unref(mvm, phy_ctxt); +} + static void iwl_mvm_remove_chanctx(struct ieee80211_hw *hw, struct ieee80211_chanctx_conf *ctx) { struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); - u16 *phy_ctxt_id = (u16 *)ctx->drv_priv; - struct iwl_mvm_phy_ctxt *phy_ctxt = &mvm->phy_ctxts[*phy_ctxt_id]; mutex_lock(&mvm->mutex); - iwl_mvm_phy_ctxt_unref(mvm, phy_ctxt); + __iwl_mvm_remove_chanctx(mvm, ctx); mutex_unlock(&mvm->mutex); } @@ -2195,17 +2670,17 @@ static void iwl_mvm_change_chanctx(struct ieee80211_hw *hw, mutex_unlock(&mvm->mutex); } -static int iwl_mvm_assign_vif_chanctx(struct ieee80211_hw *hw, - struct ieee80211_vif *vif, - struct ieee80211_chanctx_conf *ctx) +static int __iwl_mvm_assign_vif_chanctx(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_chanctx_conf *ctx, + bool switching_chanctx) { - struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); u16 *phy_ctxt_id = (u16 *)ctx->drv_priv; struct iwl_mvm_phy_ctxt *phy_ctxt = &mvm->phy_ctxts[*phy_ctxt_id]; struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); int ret; - mutex_lock(&mvm->mutex); + lockdep_assert_held(&mvm->mutex); mvmvif->phy_ctxt = phy_ctxt; @@ -2222,18 +2697,18 @@ static int iwl_mvm_assign_vif_chanctx(struct ieee80211_hw *hw, * (in bss_info_changed), similarly for IBSS. */ ret = 0; - goto out_unlock; + goto out; case NL80211_IFTYPE_STATION: case NL80211_IFTYPE_MONITOR: break; default: ret = -EINVAL; - goto out_unlock; + goto out; } ret = iwl_mvm_binding_add_vif(mvm, vif); if (ret) - goto out_unlock; + goto out; /* * Power state must be updated before quotas, @@ -2247,67 +2722,168 @@ static int iwl_mvm_assign_vif_chanctx(struct ieee80211_hw *hw, */ if (vif->type == NL80211_IFTYPE_MONITOR) { mvmvif->monitor_active = true; - ret = iwl_mvm_update_quotas(mvm, vif); + ret = iwl_mvm_update_quotas(mvm, NULL); if (ret) goto out_remove_binding; } /* Handle binding during CSA */ - if (vif->type == NL80211_IFTYPE_AP) { - iwl_mvm_update_quotas(mvm, vif); + if ((vif->type == NL80211_IFTYPE_AP) || + (switching_chanctx && (vif->type == NL80211_IFTYPE_STATION))) { + iwl_mvm_update_quotas(mvm, NULL); iwl_mvm_mac_ctxt_changed(mvm, vif, false); } - goto out_unlock; + goto out; - out_remove_binding: +out_remove_binding: iwl_mvm_binding_remove_vif(mvm, vif); iwl_mvm_power_update_mac(mvm); - out_unlock: - mutex_unlock(&mvm->mutex); +out: if (ret) mvmvif->phy_ctxt = NULL; return ret; } - -static void iwl_mvm_unassign_vif_chanctx(struct ieee80211_hw *hw, - struct ieee80211_vif *vif, - struct ieee80211_chanctx_conf *ctx) +static int iwl_mvm_assign_vif_chanctx(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_chanctx_conf *ctx) { struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); - struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + int ret; mutex_lock(&mvm->mutex); + ret = __iwl_mvm_assign_vif_chanctx(mvm, vif, ctx, false); + mutex_unlock(&mvm->mutex); + + return ret; +} + +static void __iwl_mvm_unassign_vif_chanctx(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_chanctx_conf *ctx, + bool switching_chanctx) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct ieee80211_vif *disabled_vif = NULL; + + lockdep_assert_held(&mvm->mutex); iwl_mvm_remove_time_event(mvm, mvmvif, &mvmvif->time_event_data); switch (vif->type) { case NL80211_IFTYPE_ADHOC: - goto out_unlock; + goto out; case NL80211_IFTYPE_MONITOR: mvmvif->monitor_active = false; - iwl_mvm_update_quotas(mvm, NULL); break; case NL80211_IFTYPE_AP: /* This part is triggered only during CSA */ if (!vif->csa_active || !mvmvif->ap_ibss_active) - goto out_unlock; + goto out; + + /* Set CS bit on all the stations */ + iwl_mvm_modify_all_sta_disable_tx(mvm, mvmvif, true); + + /* Save blocked iface, the timeout is set on the next beacon */ + rcu_assign_pointer(mvm->csa_tx_blocked_vif, vif); mvmvif->ap_ibss_active = false; - iwl_mvm_update_quotas(mvm, NULL); - /*TODO: bt_coex notification here? */ + break; + case NL80211_IFTYPE_STATION: + if (!switching_chanctx) + break; + + disabled_vif = vif; + + iwl_mvm_mac_ctxt_changed(mvm, vif, true); + break; default: break; } + iwl_mvm_update_quotas(mvm, disabled_vif); iwl_mvm_binding_remove_vif(mvm, vif); -out_unlock: +out: mvmvif->phy_ctxt = NULL; iwl_mvm_power_update_mac(mvm); +} + +static void iwl_mvm_unassign_vif_chanctx(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_chanctx_conf *ctx) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + mutex_lock(&mvm->mutex); + __iwl_mvm_unassign_vif_chanctx(mvm, vif, ctx, false); mutex_unlock(&mvm->mutex); } +static int iwl_mvm_switch_vif_chanctx(struct ieee80211_hw *hw, + struct ieee80211_vif_chanctx_switch *vifs, + int n_vifs, + enum ieee80211_chanctx_switch_mode mode) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + int ret; + + /* we only support SWAP_CONTEXTS and with a single-vif right now */ + if (mode != CHANCTX_SWMODE_SWAP_CONTEXTS || n_vifs > 1) + return -EOPNOTSUPP; + + mutex_lock(&mvm->mutex); + __iwl_mvm_unassign_vif_chanctx(mvm, vifs[0].vif, vifs[0].old_ctx, true); + __iwl_mvm_remove_chanctx(mvm, vifs[0].old_ctx); + + ret = __iwl_mvm_add_chanctx(mvm, vifs[0].new_ctx); + if (ret) { + IWL_ERR(mvm, "failed to add new_ctx during channel switch\n"); + goto out_reassign; + } + + ret = __iwl_mvm_assign_vif_chanctx(mvm, vifs[0].vif, vifs[0].new_ctx, + true); + if (ret) { + IWL_ERR(mvm, + "failed to assign new_ctx during channel switch\n"); + goto out_remove; + } + + /* we don't support TDLS during DCM - can be caused by channel switch */ + if (iwl_mvm_phy_ctx_count(mvm) > 1) + iwl_mvm_teardown_tdls_peers(mvm); + + goto out; + +out_remove: + __iwl_mvm_remove_chanctx(mvm, vifs[0].new_ctx); + +out_reassign: + ret = __iwl_mvm_add_chanctx(mvm, vifs[0].old_ctx); + if (ret) { + IWL_ERR(mvm, "failed to add old_ctx back after failure.\n"); + goto out_restart; + } + + ret = __iwl_mvm_assign_vif_chanctx(mvm, vifs[0].vif, vifs[0].old_ctx, + true); + if (ret) { + IWL_ERR(mvm, "failed to reassign old_ctx after failure.\n"); + goto out_restart; + } + + goto out; + +out_restart: + /* things keep failing, better restart the hw */ + iwl_mvm_nic_restart(mvm, false); + +out: + mutex_unlock(&mvm->mutex); + return ret; +} + static int iwl_mvm_set_tim(struct ieee80211_hw *hw, struct ieee80211_sta *sta, bool set) @@ -2395,15 +2971,19 @@ static void iwl_mvm_channel_switch_beacon(struct ieee80211_hw *hw, struct cfg80211_chan_def *chandef) { struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct ieee80211_vif *csa_vif; mutex_lock(&mvm->mutex); - if (WARN(mvm->csa_vif && mvm->csa_vif->csa_active, + + csa_vif = rcu_dereference_protected(mvm->csa_vif, + lockdep_is_held(&mvm->mutex)); + if (WARN(csa_vif && csa_vif->csa_active, "Another CSA is already in progress")) goto out_unlock; IWL_DEBUG_MAC80211(mvm, "CSA started to freq %d\n", chandef->center_freq1); - mvm->csa_vif = vif; + rcu_assign_pointer(mvm->csa_vif, vif); out_unlock: mutex_unlock(&mvm->mutex); @@ -2460,6 +3040,7 @@ const struct ieee80211_ops iwl_mvm_hw_ops = { .sta_rc_update = iwl_mvm_sta_rc_update, .conf_tx = iwl_mvm_mac_conf_tx, .mgd_prepare_tx = iwl_mvm_mac_mgd_prepare_tx, + .mgd_protect_tdls_discover = iwl_mvm_mac_mgd_protect_tdls_discover, .flush = iwl_mvm_mac_flush, .sched_scan_start = iwl_mvm_mac_sched_scan_start, .sched_scan_stop = iwl_mvm_mac_sched_scan_stop, @@ -2472,6 +3053,7 @@ const struct ieee80211_ops iwl_mvm_hw_ops = { .change_chanctx = iwl_mvm_change_chanctx, .assign_vif_chanctx = iwl_mvm_assign_vif_chanctx, .unassign_vif_chanctx = iwl_mvm_unassign_vif_chanctx, + .switch_vif_chanctx = iwl_mvm_switch_vif_chanctx, .start_ap = iwl_mvm_start_ap_ibss, .stop_ap = iwl_mvm_stop_ap_ibss, |