18e99ea8dSJohannes Berg // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause 28e99ea8dSJohannes Berg /* 31724fc78SEmmanuel Grumbach * Copyright (C) 2012-2014, 2018-2023 Intel Corporation 48e99ea8dSJohannes Berg * Copyright (C) 2013-2015 Intel Mobile Communications GmbH 58e99ea8dSJohannes Berg * Copyright (C) 2017 Intel Deutschland GmbH 68e99ea8dSJohannes Berg */ 7e705c121SKalle Valo #include <linux/jiffies.h> 8e705c121SKalle Valo #include <net/mac80211.h> 9e705c121SKalle Valo 109fca9d5cSJohannes Berg #include "fw/notif-wait.h" 11e705c121SKalle Valo #include "iwl-trans.h" 12e705c121SKalle Valo #include "fw-api.h" 13e705c121SKalle Valo #include "time-event.h" 14e705c121SKalle Valo #include "mvm.h" 15e705c121SKalle Valo #include "iwl-io.h" 16e705c121SKalle Valo #include "iwl-prph.h" 17e705c121SKalle Valo 18e705c121SKalle Valo /* 19e705c121SKalle Valo * For the high priority TE use a time event type that has similar priority to 20e705c121SKalle Valo * the FW's action scan priority. 21e705c121SKalle Valo */ 22e705c121SKalle Valo #define IWL_MVM_ROC_TE_TYPE_NORMAL TE_P2P_DEVICE_DISCOVERABLE 23e705c121SKalle Valo #define IWL_MVM_ROC_TE_TYPE_MGMT_TX TE_P2P_CLIENT_ASSOC 24e705c121SKalle Valo 25e705c121SKalle Valo void iwl_mvm_te_clear_data(struct iwl_mvm *mvm, 26e705c121SKalle Valo struct iwl_mvm_time_event_data *te_data) 27e705c121SKalle Valo { 28e705c121SKalle Valo lockdep_assert_held(&mvm->time_event_lock); 29e705c121SKalle Valo 30cee859feSAvraham Stern if (!te_data || !te_data->vif) 31e705c121SKalle Valo return; 32e705c121SKalle Valo 33e705c121SKalle Valo list_del(&te_data->list); 347b3954a1SIlan Peer 357b3954a1SIlan Peer /* 367b3954a1SIlan Peer * the list is only used for AUX ROC events so make sure it is always 377b3954a1SIlan Peer * initialized 387b3954a1SIlan Peer */ 397b3954a1SIlan Peer INIT_LIST_HEAD(&te_data->list); 407b3954a1SIlan Peer 41e705c121SKalle Valo te_data->running = false; 42e705c121SKalle Valo te_data->uid = 0; 43e705c121SKalle Valo te_data->id = TE_MAX; 44e705c121SKalle Valo te_data->vif = NULL; 45e705c121SKalle Valo } 46e705c121SKalle Valo 47e705c121SKalle Valo void iwl_mvm_roc_done_wk(struct work_struct *wk) 48e705c121SKalle Valo { 49e705c121SKalle Valo struct iwl_mvm *mvm = container_of(wk, struct iwl_mvm, roc_done_wk); 50e705c121SKalle Valo 51e705c121SKalle Valo /* 52998e1abaSNathan Errera * Clear the ROC_RUNNING status bit. 53e705c121SKalle Valo * This will cause the TX path to drop offchannel transmissions. 54e705c121SKalle Valo * That would also be done by mac80211, but it is racy, in particular 55e705c121SKalle Valo * in the case that the time event actually completed in the firmware 56e705c121SKalle Valo * (which is handled in iwl_mvm_te_handle_notif). 57e705c121SKalle Valo */ 5858d3bef4SEmmanuel Grumbach clear_bit(IWL_MVM_STATUS_ROC_RUNNING, &mvm->status); 59e705c121SKalle Valo 60e705c121SKalle Valo synchronize_net(); 61e705c121SKalle Valo 62e705c121SKalle Valo /* 63e705c121SKalle Valo * Flush the offchannel queue -- this is called when the time 64e705c121SKalle Valo * event finishes or is canceled, so that frames queued for it 65e705c121SKalle Valo * won't get stuck on the queue and be transmitted in the next 66e705c121SKalle Valo * time event. 67e705c121SKalle Valo */ 686c2d49fdSJohannes Berg 69f9084775SNathan Errera mutex_lock(&mvm->mutex); 706c2d49fdSJohannes Berg if (test_and_clear_bit(IWL_MVM_STATUS_NEED_FLUSH_P2P, &mvm->status)) { 716c2d49fdSJohannes Berg struct iwl_mvm_vif *mvmvif; 726c2d49fdSJohannes Berg 736c2d49fdSJohannes Berg /* 746c2d49fdSJohannes Berg * NB: access to this pointer would be racy, but the flush bit 756c2d49fdSJohannes Berg * can only be set when we had a P2P-Device VIF, and we have a 766c2d49fdSJohannes Berg * flush of this work in iwl_mvm_prepare_mac_removal() so it's 776c2d49fdSJohannes Berg * not really racy. 786c2d49fdSJohannes Berg */ 796c2d49fdSJohannes Berg 806c2d49fdSJohannes Berg if (!WARN_ON(!mvm->p2p_device_vif)) { 816c2d49fdSJohannes Berg mvmvif = iwl_mvm_vif_from_mac80211(mvm->p2p_device_vif); 82650cadb7SGregory Greenman iwl_mvm_flush_sta(mvm, &mvmvif->deflink.bcast_sta, 83650cadb7SGregory Greenman true); 846c2d49fdSJohannes Berg } 85998e1abaSNathan Errera } 86998e1abaSNathan Errera 87998e1abaSNathan Errera /* 88998e1abaSNathan Errera * Clear the ROC_AUX_RUNNING status bit. 89998e1abaSNathan Errera * This will cause the TX path to drop offchannel transmissions. 90998e1abaSNathan Errera * That would also be done by mac80211, but it is racy, in particular 91998e1abaSNathan Errera * in the case that the time event actually completed in the firmware 92998e1abaSNathan Errera * (which is handled in iwl_mvm_te_handle_notif). 93998e1abaSNathan Errera */ 94998e1abaSNathan Errera if (test_and_clear_bit(IWL_MVM_STATUS_ROC_AUX_RUNNING, &mvm->status)) { 95f9084775SNathan Errera /* do the same in case of hot spot 2.0 */ 96f9084775SNathan Errera iwl_mvm_flush_sta(mvm, &mvm->aux_sta, true); 97998e1abaSNathan Errera 98fe8b2ad3SMiri Korenblit if (mvm->mld_api_is_used) { 99fe8b2ad3SMiri Korenblit iwl_mvm_mld_rm_aux_sta(mvm); 100fe8b2ad3SMiri Korenblit goto out_unlock; 101fe8b2ad3SMiri Korenblit } 102fe8b2ad3SMiri Korenblit 1032c2c3647SNathan Errera /* In newer version of this command an aux station is added only 1042c2c3647SNathan Errera * in cases of dedicated tx queue and need to be removed in end 1052c2c3647SNathan Errera * of use */ 1061724fc78SEmmanuel Grumbach if (iwl_mvm_has_new_station_api(mvm->fw)) 1072c2c3647SNathan Errera iwl_mvm_rm_aux_sta(mvm); 1086c2d49fdSJohannes Berg } 109f9084775SNathan Errera 110fe8b2ad3SMiri Korenblit out_unlock: 111f9084775SNathan Errera mutex_unlock(&mvm->mutex); 112e705c121SKalle Valo } 113e705c121SKalle Valo 114e705c121SKalle Valo static void iwl_mvm_roc_finished(struct iwl_mvm *mvm) 115e705c121SKalle Valo { 116e705c121SKalle Valo /* 117e705c121SKalle Valo * Of course, our status bit is just as racy as mac80211, so in 118e705c121SKalle Valo * addition, fire off the work struct which will drop all frames 119e705c121SKalle Valo * from the hardware queues that made it through the race. First 120e705c121SKalle Valo * it will of course synchronize the TX path to make sure that 121e705c121SKalle Valo * any *new* TX will be rejected. 122e705c121SKalle Valo */ 123e705c121SKalle Valo schedule_work(&mvm->roc_done_wk); 124e705c121SKalle Valo } 125e705c121SKalle Valo 126e705c121SKalle Valo static void iwl_mvm_csa_noa_start(struct iwl_mvm *mvm) 127e705c121SKalle Valo { 128e705c121SKalle Valo struct ieee80211_vif *csa_vif; 129e705c121SKalle Valo 130e705c121SKalle Valo rcu_read_lock(); 131e705c121SKalle Valo 132e705c121SKalle Valo csa_vif = rcu_dereference(mvm->csa_vif); 133d0a9123eSJohannes Berg if (!csa_vif || !csa_vif->bss_conf.csa_active) 134e705c121SKalle Valo goto out_unlock; 135e705c121SKalle Valo 136e705c121SKalle Valo IWL_DEBUG_TE(mvm, "CSA NOA started\n"); 137e705c121SKalle Valo 138e705c121SKalle Valo /* 139e705c121SKalle Valo * CSA NoA is started but we still have beacons to 140e705c121SKalle Valo * transmit on the current channel. 141e705c121SKalle Valo * So we just do nothing here and the switch 142e705c121SKalle Valo * will be performed on the last TBTT. 143e705c121SKalle Valo */ 1448552a434SJohn Crispin if (!ieee80211_beacon_cntdwn_is_complete(csa_vif)) { 145e705c121SKalle Valo IWL_WARN(mvm, "CSA NOA started too early\n"); 146e705c121SKalle Valo goto out_unlock; 147e705c121SKalle Valo } 148e705c121SKalle Valo 149e705c121SKalle Valo ieee80211_csa_finish(csa_vif); 150e705c121SKalle Valo 151e705c121SKalle Valo rcu_read_unlock(); 152e705c121SKalle Valo 153e705c121SKalle Valo RCU_INIT_POINTER(mvm->csa_vif, NULL); 154e705c121SKalle Valo 155e705c121SKalle Valo return; 156e705c121SKalle Valo 157e705c121SKalle Valo out_unlock: 158e705c121SKalle Valo rcu_read_unlock(); 159e705c121SKalle Valo } 160e705c121SKalle Valo 161e705c121SKalle Valo static bool iwl_mvm_te_check_disconnect(struct iwl_mvm *mvm, 162e705c121SKalle Valo struct ieee80211_vif *vif, 163e705c121SKalle Valo const char *errmsg) 164e705c121SKalle Valo { 16519125cb0SAndrei Otcheretianski struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); 16619125cb0SAndrei Otcheretianski 167e705c121SKalle Valo if (vif->type != NL80211_IFTYPE_STATION) 168e705c121SKalle Valo return false; 16919125cb0SAndrei Otcheretianski 170f276e20bSJohannes Berg if (!mvmvif->csa_bcn_pending && vif->cfg.assoc && 17119125cb0SAndrei Otcheretianski vif->bss_conf.dtim_period) 172e705c121SKalle Valo return false; 173e705c121SKalle Valo if (errmsg) 174e705c121SKalle Valo IWL_ERR(mvm, "%s\n", errmsg); 175e705c121SKalle Valo 1767686fd52SSara Sharon if (mvmvif->csa_bcn_pending) { 1777686fd52SSara Sharon struct iwl_mvm_sta *mvmsta; 1787686fd52SSara Sharon 1797686fd52SSara Sharon rcu_read_lock(); 180650cadb7SGregory Greenman mvmsta = iwl_mvm_sta_from_staid_rcu(mvm, 181650cadb7SGregory Greenman mvmvif->deflink.ap_sta_id); 1827686fd52SSara Sharon if (!WARN_ON(!mvmsta)) 1837686fd52SSara Sharon iwl_mvm_sta_modify_disable_tx(mvm, mvmsta, false); 1847686fd52SSara Sharon rcu_read_unlock(); 1857686fd52SSara Sharon } 1867686fd52SSara Sharon 187f276e20bSJohannes Berg if (vif->cfg.assoc) { 188b537ffb6SShaul Triebitz /* 189b537ffb6SShaul Triebitz * When not associated, this will be called from 190b537ffb6SShaul Triebitz * iwl_mvm_event_mlme_callback_ini() 191b537ffb6SShaul Triebitz */ 192b537ffb6SShaul Triebitz iwl_dbg_tlv_time_point(&mvm->fwrt, 193b537ffb6SShaul Triebitz IWL_FW_INI_TIME_POINT_ASSOC_FAILED, 194b537ffb6SShaul Triebitz NULL); 195b537ffb6SShaul Triebitz } 196b537ffb6SShaul Triebitz 197e705c121SKalle Valo iwl_mvm_connection_loss(mvm, vif, errmsg); 198e705c121SKalle Valo return true; 199e705c121SKalle Valo } 200e705c121SKalle Valo 201e705c121SKalle Valo static void 202e705c121SKalle Valo iwl_mvm_te_handle_notify_csa(struct iwl_mvm *mvm, 203e705c121SKalle Valo struct iwl_mvm_time_event_data *te_data, 204e705c121SKalle Valo struct iwl_time_event_notif *notif) 205e705c121SKalle Valo { 206e705c121SKalle Valo struct ieee80211_vif *vif = te_data->vif; 207e705c121SKalle Valo struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); 208e705c121SKalle Valo 209e705c121SKalle Valo if (!notif->status) 210e705c121SKalle Valo IWL_DEBUG_TE(mvm, "CSA time event failed to start\n"); 211e705c121SKalle Valo 212e705c121SKalle Valo switch (te_data->vif->type) { 213e705c121SKalle Valo case NL80211_IFTYPE_AP: 214e705c121SKalle Valo if (!notif->status) 215e705c121SKalle Valo mvmvif->csa_failed = true; 216e705c121SKalle Valo iwl_mvm_csa_noa_start(mvm); 217e705c121SKalle Valo break; 218e705c121SKalle Valo case NL80211_IFTYPE_STATION: 219e705c121SKalle Valo if (!notif->status) { 220e705c121SKalle Valo iwl_mvm_connection_loss(mvm, vif, 221e705c121SKalle Valo "CSA TE failed to start"); 222e705c121SKalle Valo break; 223e705c121SKalle Valo } 224e705c121SKalle Valo iwl_mvm_csa_client_absent(mvm, te_data->vif); 22511af74adSAndrei Otcheretianski cancel_delayed_work(&mvmvif->csa_work); 226*a469a593SEmmanuel Grumbach ieee80211_chswitch_done(te_data->vif, true, 0); 227e705c121SKalle Valo break; 228e705c121SKalle Valo default: 229e705c121SKalle Valo /* should never happen */ 230e705c121SKalle Valo WARN_ON_ONCE(1); 231e705c121SKalle Valo break; 232e705c121SKalle Valo } 233e705c121SKalle Valo 234e705c121SKalle Valo /* we don't need it anymore */ 235e705c121SKalle Valo iwl_mvm_te_clear_data(mvm, te_data); 236e705c121SKalle Valo } 237e705c121SKalle Valo 238e705c121SKalle Valo static void iwl_mvm_te_check_trigger(struct iwl_mvm *mvm, 239e705c121SKalle Valo struct iwl_time_event_notif *notif, 240e705c121SKalle Valo struct iwl_mvm_time_event_data *te_data) 241e705c121SKalle Valo { 242e705c121SKalle Valo struct iwl_fw_dbg_trigger_tlv *trig; 243e705c121SKalle Valo struct iwl_fw_dbg_trigger_time_event *te_trig; 244e705c121SKalle Valo int i; 245e705c121SKalle Valo 2466c042d75SSara Sharon trig = iwl_fw_dbg_trigger_on(&mvm->fwrt, 2477174beb6SJohannes Berg ieee80211_vif_to_wdev(te_data->vif), 2486c042d75SSara Sharon FW_DBG_TRIGGER_TIME_EVENT); 2496c042d75SSara Sharon if (!trig) 250e705c121SKalle Valo return; 251e705c121SKalle Valo 2526c042d75SSara Sharon te_trig = (void *)trig->data; 2536c042d75SSara Sharon 254e705c121SKalle Valo for (i = 0; i < ARRAY_SIZE(te_trig->time_events); i++) { 255e705c121SKalle Valo u32 trig_te_id = le32_to_cpu(te_trig->time_events[i].id); 256e705c121SKalle Valo u32 trig_action_bitmap = 257e705c121SKalle Valo le32_to_cpu(te_trig->time_events[i].action_bitmap); 258e705c121SKalle Valo u32 trig_status_bitmap = 259e705c121SKalle Valo le32_to_cpu(te_trig->time_events[i].status_bitmap); 260e705c121SKalle Valo 261e705c121SKalle Valo if (trig_te_id != te_data->id || 262e705c121SKalle Valo !(trig_action_bitmap & le32_to_cpu(notif->action)) || 263e705c121SKalle Valo !(trig_status_bitmap & BIT(le32_to_cpu(notif->status)))) 264e705c121SKalle Valo continue; 265e705c121SKalle Valo 2667174beb6SJohannes Berg iwl_fw_dbg_collect_trig(&mvm->fwrt, trig, 267e705c121SKalle Valo "Time event %d Action 0x%x received status: %d", 268e705c121SKalle Valo te_data->id, 269e705c121SKalle Valo le32_to_cpu(notif->action), 270e705c121SKalle Valo le32_to_cpu(notif->status)); 271e705c121SKalle Valo break; 272e705c121SKalle Valo } 273e705c121SKalle Valo } 274e705c121SKalle Valo 275a76b5731SAvraham Stern static void iwl_mvm_p2p_roc_finished(struct iwl_mvm *mvm) 276a76b5731SAvraham Stern { 277a76b5731SAvraham Stern /* 278a76b5731SAvraham Stern * If the IWL_MVM_STATUS_NEED_FLUSH_P2P is already set, then the 279a76b5731SAvraham Stern * roc_done_wk is already scheduled or running, so don't schedule it 280a76b5731SAvraham Stern * again to avoid a race where the roc_done_wk clears this bit after 281a76b5731SAvraham Stern * it is set here, affecting the next run of the roc_done_wk. 282a76b5731SAvraham Stern */ 283a76b5731SAvraham Stern if (!test_and_set_bit(IWL_MVM_STATUS_NEED_FLUSH_P2P, &mvm->status)) 284a76b5731SAvraham Stern iwl_mvm_roc_finished(mvm); 285a76b5731SAvraham Stern } 286a76b5731SAvraham Stern 287e705c121SKalle Valo /* 288e705c121SKalle Valo * Handles a FW notification for an event that is known to the driver. 289e705c121SKalle Valo * 290e705c121SKalle Valo * @mvm: the mvm component 291e705c121SKalle Valo * @te_data: the time event data 292e705c121SKalle Valo * @notif: the notification data corresponding the time event data. 293e705c121SKalle Valo */ 294e705c121SKalle Valo static void iwl_mvm_te_handle_notif(struct iwl_mvm *mvm, 295e705c121SKalle Valo struct iwl_mvm_time_event_data *te_data, 296e705c121SKalle Valo struct iwl_time_event_notif *notif) 297e705c121SKalle Valo { 298e705c121SKalle Valo lockdep_assert_held(&mvm->time_event_lock); 299e705c121SKalle Valo 300e705c121SKalle Valo IWL_DEBUG_TE(mvm, "Handle time event notif - UID = 0x%x action %d\n", 301e705c121SKalle Valo le32_to_cpu(notif->unique_id), 302e705c121SKalle Valo le32_to_cpu(notif->action)); 303e705c121SKalle Valo 304e705c121SKalle Valo iwl_mvm_te_check_trigger(mvm, notif, te_data); 305e705c121SKalle Valo 306e705c121SKalle Valo /* 307e705c121SKalle Valo * The FW sends the start/end time event notifications even for events 308e705c121SKalle Valo * that it fails to schedule. This is indicated in the status field of 309e705c121SKalle Valo * the notification. This happens in cases that the scheduler cannot 310e705c121SKalle Valo * find a schedule that can handle the event (for example requesting a 311e705c121SKalle Valo * P2P Device discoveribility, while there are other higher priority 312e705c121SKalle Valo * events in the system). 313e705c121SKalle Valo */ 314e705c121SKalle Valo if (!le32_to_cpu(notif->status)) { 315e705c121SKalle Valo const char *msg; 316e705c121SKalle Valo 317e705c121SKalle Valo if (notif->action & cpu_to_le32(TE_V2_NOTIF_HOST_EVENT_START)) 318e705c121SKalle Valo msg = "Time Event start notification failure"; 319e705c121SKalle Valo else 320e705c121SKalle Valo msg = "Time Event end notification failure"; 321e705c121SKalle Valo 322e705c121SKalle Valo IWL_DEBUG_TE(mvm, "%s\n", msg); 323e705c121SKalle Valo 324e705c121SKalle Valo if (iwl_mvm_te_check_disconnect(mvm, te_data->vif, msg)) { 325e705c121SKalle Valo iwl_mvm_te_clear_data(mvm, te_data); 326e705c121SKalle Valo return; 327e705c121SKalle Valo } 328e705c121SKalle Valo } 329e705c121SKalle Valo 330e705c121SKalle Valo if (le32_to_cpu(notif->action) & TE_V2_NOTIF_HOST_EVENT_END) { 331e705c121SKalle Valo IWL_DEBUG_TE(mvm, 332e705c121SKalle Valo "TE ended - current time %lu, estimated end %lu\n", 333e705c121SKalle Valo jiffies, te_data->end_jiffies); 334e705c121SKalle Valo 335e705c121SKalle Valo switch (te_data->vif->type) { 336e705c121SKalle Valo case NL80211_IFTYPE_P2P_DEVICE: 337e705c121SKalle Valo ieee80211_remain_on_channel_expired(mvm->hw); 338a76b5731SAvraham Stern iwl_mvm_p2p_roc_finished(mvm); 339e705c121SKalle Valo break; 340e705c121SKalle Valo case NL80211_IFTYPE_STATION: 341e705c121SKalle Valo /* 342cc61d3ceSEmmanuel Grumbach * If we are switching channel, don't disconnect 343cc61d3ceSEmmanuel Grumbach * if the time event is already done. Beacons can 344cc61d3ceSEmmanuel Grumbach * be delayed a bit after the switch. 345cc61d3ceSEmmanuel Grumbach */ 346cc61d3ceSEmmanuel Grumbach if (te_data->id == TE_CHANNEL_SWITCH_PERIOD) { 347cc61d3ceSEmmanuel Grumbach IWL_DEBUG_TE(mvm, 348cc61d3ceSEmmanuel Grumbach "No beacon heard and the CS time event is over, don't disconnect\n"); 349cc61d3ceSEmmanuel Grumbach break; 350cc61d3ceSEmmanuel Grumbach } 351cc61d3ceSEmmanuel Grumbach 352cc61d3ceSEmmanuel Grumbach /* 353e705c121SKalle Valo * By now, we should have finished association 354e705c121SKalle Valo * and know the dtim period. 355e705c121SKalle Valo */ 356e705c121SKalle Valo iwl_mvm_te_check_disconnect(mvm, te_data->vif, 357f276e20bSJohannes Berg !te_data->vif->cfg.assoc ? 358976ac0afSShaul Triebitz "Not associated and the time event is over already..." : 35919125cb0SAndrei Otcheretianski "No beacon heard and the time event is over already..."); 360e705c121SKalle Valo break; 361e705c121SKalle Valo default: 362e705c121SKalle Valo break; 363e705c121SKalle Valo } 364e705c121SKalle Valo 365e705c121SKalle Valo iwl_mvm_te_clear_data(mvm, te_data); 366e705c121SKalle Valo } else if (le32_to_cpu(notif->action) & TE_V2_NOTIF_HOST_EVENT_START) { 367e705c121SKalle Valo te_data->running = true; 368e705c121SKalle Valo te_data->end_jiffies = TU_TO_EXP_TIME(te_data->duration); 369e705c121SKalle Valo 370e705c121SKalle Valo if (te_data->vif->type == NL80211_IFTYPE_P2P_DEVICE) { 371e705c121SKalle Valo set_bit(IWL_MVM_STATUS_ROC_RUNNING, &mvm->status); 372e705c121SKalle Valo ieee80211_ready_on_channel(mvm->hw); 373e705c121SKalle Valo } else if (te_data->id == TE_CHANNEL_SWITCH_PERIOD) { 374e705c121SKalle Valo iwl_mvm_te_handle_notify_csa(mvm, te_data, notif); 375e705c121SKalle Valo } 376e705c121SKalle Valo } else { 377e705c121SKalle Valo IWL_WARN(mvm, "Got TE with unknown action\n"); 378e705c121SKalle Valo } 379e705c121SKalle Valo } 380e705c121SKalle Valo 381e705c121SKalle Valo /* 382e705c121SKalle Valo * Handle A Aux ROC time event 383e705c121SKalle Valo */ 384e705c121SKalle Valo static int iwl_mvm_aux_roc_te_handle_notif(struct iwl_mvm *mvm, 385e705c121SKalle Valo struct iwl_time_event_notif *notif) 386e705c121SKalle Valo { 3876d7cb4a6SJakob Koschel struct iwl_mvm_time_event_data *aux_roc_te = NULL, *te_data; 388e705c121SKalle Valo 3896d7cb4a6SJakob Koschel list_for_each_entry(te_data, &mvm->aux_roc_te_list, list) { 390e705c121SKalle Valo if (le32_to_cpu(notif->unique_id) == te_data->uid) { 3916d7cb4a6SJakob Koschel aux_roc_te = te_data; 392e705c121SKalle Valo break; 393e705c121SKalle Valo } 394e705c121SKalle Valo } 395e705c121SKalle Valo if (!aux_roc_te) /* Not a Aux ROC time event */ 396e705c121SKalle Valo return -EINVAL; 397e705c121SKalle Valo 398e705c121SKalle Valo iwl_mvm_te_check_trigger(mvm, notif, te_data); 399e705c121SKalle Valo 400e705c121SKalle Valo IWL_DEBUG_TE(mvm, 4015151ad95SMatti Gottlieb "Aux ROC time event notification - UID = 0x%x action %d (error = %d)\n", 402e705c121SKalle Valo le32_to_cpu(notif->unique_id), 4035151ad95SMatti Gottlieb le32_to_cpu(notif->action), le32_to_cpu(notif->status)); 404e705c121SKalle Valo 4055151ad95SMatti Gottlieb if (!le32_to_cpu(notif->status) || 4065151ad95SMatti Gottlieb le32_to_cpu(notif->action) == TE_V2_NOTIF_HOST_EVENT_END) { 407e705c121SKalle Valo /* End TE, notify mac80211 */ 408e705c121SKalle Valo ieee80211_remain_on_channel_expired(mvm->hw); 409e705c121SKalle Valo iwl_mvm_roc_finished(mvm); /* flush aux queue */ 410e705c121SKalle Valo list_del(&te_data->list); /* remove from list */ 411e705c121SKalle Valo te_data->running = false; 412e705c121SKalle Valo te_data->vif = NULL; 413e705c121SKalle Valo te_data->uid = 0; 414e705c121SKalle Valo te_data->id = TE_MAX; 415e705c121SKalle Valo } else if (le32_to_cpu(notif->action) == TE_V2_NOTIF_HOST_EVENT_START) { 416e705c121SKalle Valo set_bit(IWL_MVM_STATUS_ROC_AUX_RUNNING, &mvm->status); 417e705c121SKalle Valo te_data->running = true; 418e705c121SKalle Valo ieee80211_ready_on_channel(mvm->hw); /* Start TE */ 419e705c121SKalle Valo } else { 420e705c121SKalle Valo IWL_DEBUG_TE(mvm, 421e705c121SKalle Valo "ERROR: Unknown Aux ROC Time Event (action = %d)\n", 422e705c121SKalle Valo le32_to_cpu(notif->action)); 423e705c121SKalle Valo return -EINVAL; 424e705c121SKalle Valo } 425e705c121SKalle Valo 426e705c121SKalle Valo return 0; 427e705c121SKalle Valo } 428e705c121SKalle Valo 429e705c121SKalle Valo /* 430e705c121SKalle Valo * The Rx handler for time event notifications 431e705c121SKalle Valo */ 432e705c121SKalle Valo void iwl_mvm_rx_time_event_notif(struct iwl_mvm *mvm, 433e705c121SKalle Valo struct iwl_rx_cmd_buffer *rxb) 434e705c121SKalle Valo { 435e705c121SKalle Valo struct iwl_rx_packet *pkt = rxb_addr(rxb); 436e705c121SKalle Valo struct iwl_time_event_notif *notif = (void *)pkt->data; 437e705c121SKalle Valo struct iwl_mvm_time_event_data *te_data, *tmp; 438e705c121SKalle Valo 439e705c121SKalle Valo IWL_DEBUG_TE(mvm, "Time event notification - UID = 0x%x action %d\n", 440e705c121SKalle Valo le32_to_cpu(notif->unique_id), 441e705c121SKalle Valo le32_to_cpu(notif->action)); 442e705c121SKalle Valo 443e705c121SKalle Valo spin_lock_bh(&mvm->time_event_lock); 444e705c121SKalle Valo /* This time event is triggered for Aux ROC request */ 445e705c121SKalle Valo if (!iwl_mvm_aux_roc_te_handle_notif(mvm, notif)) 446e705c121SKalle Valo goto unlock; 447e705c121SKalle Valo 448e705c121SKalle Valo list_for_each_entry_safe(te_data, tmp, &mvm->time_event_list, list) { 449e705c121SKalle Valo if (le32_to_cpu(notif->unique_id) == te_data->uid) 450e705c121SKalle Valo iwl_mvm_te_handle_notif(mvm, te_data, notif); 451e705c121SKalle Valo } 452e705c121SKalle Valo unlock: 453e705c121SKalle Valo spin_unlock_bh(&mvm->time_event_lock); 454e705c121SKalle Valo } 455e705c121SKalle Valo 456e705c121SKalle Valo static bool iwl_mvm_te_notif(struct iwl_notif_wait_data *notif_wait, 457e705c121SKalle Valo struct iwl_rx_packet *pkt, void *data) 458e705c121SKalle Valo { 459e705c121SKalle Valo struct iwl_mvm *mvm = 460e705c121SKalle Valo container_of(notif_wait, struct iwl_mvm, notif_wait); 461e705c121SKalle Valo struct iwl_mvm_time_event_data *te_data = data; 462e705c121SKalle Valo struct iwl_time_event_notif *resp; 463e705c121SKalle Valo int resp_len = iwl_rx_packet_payload_len(pkt); 464e705c121SKalle Valo 465e705c121SKalle Valo if (WARN_ON(pkt->hdr.cmd != TIME_EVENT_NOTIFICATION)) 466e705c121SKalle Valo return true; 467e705c121SKalle Valo 468e705c121SKalle Valo if (WARN_ON_ONCE(resp_len != sizeof(*resp))) { 469e705c121SKalle Valo IWL_ERR(mvm, "Invalid TIME_EVENT_NOTIFICATION response\n"); 470e705c121SKalle Valo return true; 471e705c121SKalle Valo } 472e705c121SKalle Valo 473e705c121SKalle Valo resp = (void *)pkt->data; 474e705c121SKalle Valo 475e705c121SKalle Valo /* te_data->uid is already set in the TIME_EVENT_CMD response */ 476e705c121SKalle Valo if (le32_to_cpu(resp->unique_id) != te_data->uid) 477e705c121SKalle Valo return false; 478e705c121SKalle Valo 479e705c121SKalle Valo IWL_DEBUG_TE(mvm, "TIME_EVENT_NOTIFICATION response - UID = 0x%x\n", 480e705c121SKalle Valo te_data->uid); 481e705c121SKalle Valo if (!resp->status) 482e705c121SKalle Valo IWL_ERR(mvm, 483e705c121SKalle Valo "TIME_EVENT_NOTIFICATION received but not executed\n"); 484e705c121SKalle Valo 485e705c121SKalle Valo return true; 486e705c121SKalle Valo } 487e705c121SKalle Valo 488e705c121SKalle Valo static bool iwl_mvm_time_event_response(struct iwl_notif_wait_data *notif_wait, 489e705c121SKalle Valo struct iwl_rx_packet *pkt, void *data) 490e705c121SKalle Valo { 491e705c121SKalle Valo struct iwl_mvm *mvm = 492e705c121SKalle Valo container_of(notif_wait, struct iwl_mvm, notif_wait); 493e705c121SKalle Valo struct iwl_mvm_time_event_data *te_data = data; 494e705c121SKalle Valo struct iwl_time_event_resp *resp; 495e705c121SKalle Valo int resp_len = iwl_rx_packet_payload_len(pkt); 496e705c121SKalle Valo 497e705c121SKalle Valo if (WARN_ON(pkt->hdr.cmd != TIME_EVENT_CMD)) 498e705c121SKalle Valo return true; 499e705c121SKalle Valo 500e705c121SKalle Valo if (WARN_ON_ONCE(resp_len != sizeof(*resp))) { 501e705c121SKalle Valo IWL_ERR(mvm, "Invalid TIME_EVENT_CMD response\n"); 502e705c121SKalle Valo return true; 503e705c121SKalle Valo } 504e705c121SKalle Valo 505e705c121SKalle Valo resp = (void *)pkt->data; 506e705c121SKalle Valo 507e705c121SKalle Valo /* we should never get a response to another TIME_EVENT_CMD here */ 508e705c121SKalle Valo if (WARN_ON_ONCE(le32_to_cpu(resp->id) != te_data->id)) 509e705c121SKalle Valo return false; 510e705c121SKalle Valo 511e705c121SKalle Valo te_data->uid = le32_to_cpu(resp->unique_id); 512e705c121SKalle Valo IWL_DEBUG_TE(mvm, "TIME_EVENT_CMD response - UID = 0x%x\n", 513e705c121SKalle Valo te_data->uid); 514e705c121SKalle Valo return true; 515e705c121SKalle Valo } 516e705c121SKalle Valo 517e705c121SKalle Valo static int iwl_mvm_time_event_send_add(struct iwl_mvm *mvm, 518e705c121SKalle Valo struct ieee80211_vif *vif, 519e705c121SKalle Valo struct iwl_mvm_time_event_data *te_data, 520e705c121SKalle Valo struct iwl_time_event_cmd *te_cmd) 521e705c121SKalle Valo { 522e705c121SKalle Valo static const u16 time_event_response[] = { TIME_EVENT_CMD }; 523e705c121SKalle Valo struct iwl_notification_wait wait_time_event; 524e705c121SKalle Valo int ret; 525e705c121SKalle Valo 526e705c121SKalle Valo lockdep_assert_held(&mvm->mutex); 527e705c121SKalle Valo 528e705c121SKalle Valo IWL_DEBUG_TE(mvm, "Add new TE, duration %d TU\n", 529e705c121SKalle Valo le32_to_cpu(te_cmd->duration)); 530e705c121SKalle Valo 531e705c121SKalle Valo spin_lock_bh(&mvm->time_event_lock); 532e705c121SKalle Valo if (WARN_ON(te_data->id != TE_MAX)) { 533e705c121SKalle Valo spin_unlock_bh(&mvm->time_event_lock); 534e705c121SKalle Valo return -EIO; 535e705c121SKalle Valo } 536e705c121SKalle Valo te_data->vif = vif; 537e705c121SKalle Valo te_data->duration = le32_to_cpu(te_cmd->duration); 538e705c121SKalle Valo te_data->id = le32_to_cpu(te_cmd->id); 539e705c121SKalle Valo list_add_tail(&te_data->list, &mvm->time_event_list); 540e705c121SKalle Valo spin_unlock_bh(&mvm->time_event_lock); 541e705c121SKalle Valo 542e705c121SKalle Valo /* 543e705c121SKalle Valo * Use a notification wait, which really just processes the 544e705c121SKalle Valo * command response and doesn't wait for anything, in order 545e705c121SKalle Valo * to be able to process the response and get the UID inside 546e705c121SKalle Valo * the RX path. Using CMD_WANT_SKB doesn't work because it 547e705c121SKalle Valo * stores the buffer and then wakes up this thread, by which 548e705c121SKalle Valo * time another notification (that the time event started) 549e705c121SKalle Valo * might already be processed unsuccessfully. 550e705c121SKalle Valo */ 551e705c121SKalle Valo iwl_init_notification_wait(&mvm->notif_wait, &wait_time_event, 552e705c121SKalle Valo time_event_response, 553e705c121SKalle Valo ARRAY_SIZE(time_event_response), 554e705c121SKalle Valo iwl_mvm_time_event_response, te_data); 555e705c121SKalle Valo 556e705c121SKalle Valo ret = iwl_mvm_send_cmd_pdu(mvm, TIME_EVENT_CMD, 0, 557e705c121SKalle Valo sizeof(*te_cmd), te_cmd); 558e705c121SKalle Valo if (ret) { 559e705c121SKalle Valo IWL_ERR(mvm, "Couldn't send TIME_EVENT_CMD: %d\n", ret); 560e705c121SKalle Valo iwl_remove_notification(&mvm->notif_wait, &wait_time_event); 561e705c121SKalle Valo goto out_clear_te; 562e705c121SKalle Valo } 563e705c121SKalle Valo 564e705c121SKalle Valo /* No need to wait for anything, so just pass 1 (0 isn't valid) */ 565e705c121SKalle Valo ret = iwl_wait_notification(&mvm->notif_wait, &wait_time_event, 1); 566e705c121SKalle Valo /* should never fail */ 567e705c121SKalle Valo WARN_ON_ONCE(ret); 568e705c121SKalle Valo 569e705c121SKalle Valo if (ret) { 570e705c121SKalle Valo out_clear_te: 571e705c121SKalle Valo spin_lock_bh(&mvm->time_event_lock); 572e705c121SKalle Valo iwl_mvm_te_clear_data(mvm, te_data); 573e705c121SKalle Valo spin_unlock_bh(&mvm->time_event_lock); 574e705c121SKalle Valo } 575e705c121SKalle Valo return ret; 576e705c121SKalle Valo } 577e705c121SKalle Valo 578e705c121SKalle Valo void iwl_mvm_protect_session(struct iwl_mvm *mvm, 579e705c121SKalle Valo struct ieee80211_vif *vif, 580e705c121SKalle Valo u32 duration, u32 min_duration, 581e705c121SKalle Valo u32 max_delay, bool wait_for_notif) 582e705c121SKalle Valo { 583e705c121SKalle Valo struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); 584e705c121SKalle Valo struct iwl_mvm_time_event_data *te_data = &mvmvif->time_event_data; 585e705c121SKalle Valo const u16 te_notif_response[] = { TIME_EVENT_NOTIFICATION }; 586e705c121SKalle Valo struct iwl_notification_wait wait_te_notif; 587e705c121SKalle Valo struct iwl_time_event_cmd time_cmd = {}; 588e705c121SKalle Valo 589e705c121SKalle Valo lockdep_assert_held(&mvm->mutex); 590e705c121SKalle Valo 591e705c121SKalle Valo if (te_data->running && 592e705c121SKalle Valo time_after(te_data->end_jiffies, TU_TO_EXP_TIME(min_duration))) { 593e705c121SKalle Valo IWL_DEBUG_TE(mvm, "We have enough time in the current TE: %u\n", 594e705c121SKalle Valo jiffies_to_msecs(te_data->end_jiffies - jiffies)); 595e705c121SKalle Valo return; 596e705c121SKalle Valo } 597e705c121SKalle Valo 598e705c121SKalle Valo if (te_data->running) { 599e705c121SKalle Valo IWL_DEBUG_TE(mvm, "extend 0x%x: only %u ms left\n", 600e705c121SKalle Valo te_data->uid, 601e705c121SKalle Valo jiffies_to_msecs(te_data->end_jiffies - jiffies)); 602e705c121SKalle Valo /* 603e705c121SKalle Valo * we don't have enough time 604e705c121SKalle Valo * cancel the current TE and issue a new one 605e705c121SKalle Valo * Of course it would be better to remove the old one only 606e705c121SKalle Valo * when the new one is added, but we don't care if we are off 607e705c121SKalle Valo * channel for a bit. All we need to do, is not to return 608e705c121SKalle Valo * before we actually begin to be on the channel. 609e705c121SKalle Valo */ 610e705c121SKalle Valo iwl_mvm_stop_session_protection(mvm, vif); 611e705c121SKalle Valo } 612e705c121SKalle Valo 613e705c121SKalle Valo time_cmd.action = cpu_to_le32(FW_CTXT_ACTION_ADD); 614e705c121SKalle Valo time_cmd.id_and_color = 615e705c121SKalle Valo cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, mvmvif->color)); 616e705c121SKalle Valo time_cmd.id = cpu_to_le32(TE_BSS_STA_AGGRESSIVE_ASSOC); 617e705c121SKalle Valo 618e705c121SKalle Valo time_cmd.apply_time = cpu_to_le32(0); 619e705c121SKalle Valo 620e705c121SKalle Valo time_cmd.max_frags = TE_V2_FRAG_NONE; 621e705c121SKalle Valo time_cmd.max_delay = cpu_to_le32(max_delay); 622e705c121SKalle Valo /* TODO: why do we need to interval = bi if it is not periodic? */ 623e705c121SKalle Valo time_cmd.interval = cpu_to_le32(1); 624e705c121SKalle Valo time_cmd.duration = cpu_to_le32(duration); 625e705c121SKalle Valo time_cmd.repeat = 1; 626e705c121SKalle Valo time_cmd.policy = cpu_to_le16(TE_V2_NOTIF_HOST_EVENT_START | 627e705c121SKalle Valo TE_V2_NOTIF_HOST_EVENT_END | 62840d53f4aSAndrei Otcheretianski TE_V2_START_IMMEDIATELY); 629e705c121SKalle Valo 630e705c121SKalle Valo if (!wait_for_notif) { 631e705c121SKalle Valo iwl_mvm_time_event_send_add(mvm, vif, te_data, &time_cmd); 632e705c121SKalle Valo return; 633e705c121SKalle Valo } 634e705c121SKalle Valo 635e705c121SKalle Valo /* 636e705c121SKalle Valo * Create notification_wait for the TIME_EVENT_NOTIFICATION to use 637e705c121SKalle Valo * right after we send the time event 638e705c121SKalle Valo */ 639e705c121SKalle Valo iwl_init_notification_wait(&mvm->notif_wait, &wait_te_notif, 640e705c121SKalle Valo te_notif_response, 641e705c121SKalle Valo ARRAY_SIZE(te_notif_response), 642e705c121SKalle Valo iwl_mvm_te_notif, te_data); 643e705c121SKalle Valo 644e705c121SKalle Valo /* If TE was sent OK - wait for the notification that started */ 645e705c121SKalle Valo if (iwl_mvm_time_event_send_add(mvm, vif, te_data, &time_cmd)) { 646e705c121SKalle Valo IWL_ERR(mvm, "Failed to add TE to protect session\n"); 647e705c121SKalle Valo iwl_remove_notification(&mvm->notif_wait, &wait_te_notif); 648e705c121SKalle Valo } else if (iwl_wait_notification(&mvm->notif_wait, &wait_te_notif, 649e705c121SKalle Valo TU_TO_JIFFIES(max_delay))) { 650e705c121SKalle Valo IWL_ERR(mvm, "Failed to protect session until TE\n"); 651e705c121SKalle Valo } 652e705c121SKalle Valo } 653e705c121SKalle Valo 6541cf260e3SEmmanuel Grumbach static void iwl_mvm_cancel_session_protection(struct iwl_mvm *mvm, 6557b3954a1SIlan Peer struct iwl_mvm_vif *mvmvif, 6567b3954a1SIlan Peer u32 id) 6571cf260e3SEmmanuel Grumbach { 6581cf260e3SEmmanuel Grumbach struct iwl_mvm_session_prot_cmd cmd = { 6591cf260e3SEmmanuel Grumbach .id_and_color = 6601cf260e3SEmmanuel Grumbach cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, 6611cf260e3SEmmanuel Grumbach mvmvif->color)), 6621cf260e3SEmmanuel Grumbach .action = cpu_to_le32(FW_CTXT_ACTION_REMOVE), 6637b3954a1SIlan Peer .conf_id = cpu_to_le32(id), 6641cf260e3SEmmanuel Grumbach }; 6651cf260e3SEmmanuel Grumbach int ret; 6661cf260e3SEmmanuel Grumbach 667f0c86427SJohannes Berg ret = iwl_mvm_send_cmd_pdu(mvm, 668f0c86427SJohannes Berg WIDE_ID(MAC_CONF_GROUP, SESSION_PROTECTION_CMD), 6691cf260e3SEmmanuel Grumbach 0, sizeof(cmd), &cmd); 6701cf260e3SEmmanuel Grumbach if (ret) 6711cf260e3SEmmanuel Grumbach IWL_ERR(mvm, 6721cf260e3SEmmanuel Grumbach "Couldn't send the SESSION_PROTECTION_CMD: %d\n", ret); 6731cf260e3SEmmanuel Grumbach } 6741cf260e3SEmmanuel Grumbach 675e705c121SKalle Valo static bool __iwl_mvm_remove_time_event(struct iwl_mvm *mvm, 676e705c121SKalle Valo struct iwl_mvm_time_event_data *te_data, 677e705c121SKalle Valo u32 *uid) 678e705c121SKalle Valo { 679e705c121SKalle Valo u32 id; 68024d5f16eSIlan Peer struct iwl_mvm_vif *mvmvif; 6817b3954a1SIlan Peer enum nl80211_iftype iftype; 6827b3954a1SIlan Peer 6837b3954a1SIlan Peer if (!te_data->vif) 6847b3954a1SIlan Peer return false; 6857b3954a1SIlan Peer 68624d5f16eSIlan Peer mvmvif = iwl_mvm_vif_from_mac80211(te_data->vif); 6877b3954a1SIlan Peer iftype = te_data->vif->type; 688e705c121SKalle Valo 689e705c121SKalle Valo /* 690e705c121SKalle Valo * It is possible that by the time we got to this point the time 691e705c121SKalle Valo * event was already removed. 692e705c121SKalle Valo */ 693e705c121SKalle Valo spin_lock_bh(&mvm->time_event_lock); 694e705c121SKalle Valo 695e705c121SKalle Valo /* Save time event uid before clearing its data */ 696e705c121SKalle Valo *uid = te_data->uid; 697e705c121SKalle Valo id = te_data->id; 698e705c121SKalle Valo 699e705c121SKalle Valo /* 700e705c121SKalle Valo * The clear_data function handles time events that were already removed 701e705c121SKalle Valo */ 702e705c121SKalle Valo iwl_mvm_te_clear_data(mvm, te_data); 703e705c121SKalle Valo spin_unlock_bh(&mvm->time_event_lock); 704e705c121SKalle Valo 705f0337cb4SAvraham Stern /* When session protection is used, the te_data->id field 7061cf260e3SEmmanuel Grumbach * is reused to save session protection's configuration. 707f0337cb4SAvraham Stern * For AUX ROC, HOT_SPOT_CMD is used and the te_data->id field is set 708f0337cb4SAvraham Stern * to HOT_SPOT_CMD. 7091cf260e3SEmmanuel Grumbach */ 7101cf260e3SEmmanuel Grumbach if (fw_has_capa(&mvm->fw->ucode_capa, 711f0337cb4SAvraham Stern IWL_UCODE_TLV_CAPA_SESSION_PROT_CMD) && 712f0337cb4SAvraham Stern id != HOT_SPOT_CMD) { 7131cf260e3SEmmanuel Grumbach if (mvmvif && id < SESSION_PROTECT_CONF_MAX_ID) { 7141cf260e3SEmmanuel Grumbach /* Session protection is still ongoing. Cancel it */ 7157b3954a1SIlan Peer iwl_mvm_cancel_session_protection(mvm, mvmvif, id); 7167b3954a1SIlan Peer if (iftype == NL80211_IFTYPE_P2P_DEVICE) { 717a76b5731SAvraham Stern iwl_mvm_p2p_roc_finished(mvm); 7181cf260e3SEmmanuel Grumbach } 7191cf260e3SEmmanuel Grumbach } 7201cf260e3SEmmanuel Grumbach return false; 7211cf260e3SEmmanuel Grumbach } else { 7221cf260e3SEmmanuel Grumbach /* It is possible that by the time we try to remove it, the 7231cf260e3SEmmanuel Grumbach * time event has already ended and removed. In such a case 7241cf260e3SEmmanuel Grumbach * there is no need to send a removal command. 725e705c121SKalle Valo */ 726e705c121SKalle Valo if (id == TE_MAX) { 727e705c121SKalle Valo IWL_DEBUG_TE(mvm, "TE 0x%x has already ended\n", *uid); 728e705c121SKalle Valo return false; 729e705c121SKalle Valo } 7301cf260e3SEmmanuel Grumbach } 731e705c121SKalle Valo 732e705c121SKalle Valo return true; 733e705c121SKalle Valo } 734e705c121SKalle Valo 735e705c121SKalle Valo /* 736e705c121SKalle Valo * Explicit request to remove a aux roc time event. The removal of a time 737e705c121SKalle Valo * event needs to be synchronized with the flow of a time event's end 738e705c121SKalle Valo * notification, which also removes the time event from the op mode 739e705c121SKalle Valo * data structures. 740e705c121SKalle Valo */ 741e705c121SKalle Valo static void iwl_mvm_remove_aux_roc_te(struct iwl_mvm *mvm, 742e705c121SKalle Valo struct iwl_mvm_vif *mvmvif, 743e705c121SKalle Valo struct iwl_mvm_time_event_data *te_data) 744e705c121SKalle Valo { 745e705c121SKalle Valo struct iwl_hs20_roc_req aux_cmd = {}; 74657e861d9SDavid Spinadel u16 len = sizeof(aux_cmd) - iwl_mvm_chan_info_padding(mvm); 74757e861d9SDavid Spinadel 748e705c121SKalle Valo u32 uid; 749e705c121SKalle Valo int ret; 750e705c121SKalle Valo 751e705c121SKalle Valo if (!__iwl_mvm_remove_time_event(mvm, te_data, &uid)) 752e705c121SKalle Valo return; 753e705c121SKalle Valo 754e705c121SKalle Valo aux_cmd.event_unique_id = cpu_to_le32(uid); 755e705c121SKalle Valo aux_cmd.action = cpu_to_le32(FW_CTXT_ACTION_REMOVE); 756e705c121SKalle Valo aux_cmd.id_and_color = 757e705c121SKalle Valo cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, mvmvif->color)); 758e705c121SKalle Valo IWL_DEBUG_TE(mvm, "Removing BSS AUX ROC TE 0x%x\n", 759e705c121SKalle Valo le32_to_cpu(aux_cmd.event_unique_id)); 760e705c121SKalle Valo ret = iwl_mvm_send_cmd_pdu(mvm, HOT_SPOT_CMD, 0, 76157e861d9SDavid Spinadel len, &aux_cmd); 762e705c121SKalle Valo 763e705c121SKalle Valo if (WARN_ON(ret)) 764e705c121SKalle Valo return; 765e705c121SKalle Valo } 766e705c121SKalle Valo 767e705c121SKalle Valo /* 768e705c121SKalle Valo * Explicit request to remove a time event. The removal of a time event needs to 769e705c121SKalle Valo * be synchronized with the flow of a time event's end notification, which also 770e705c121SKalle Valo * removes the time event from the op mode data structures. 771e705c121SKalle Valo */ 772e705c121SKalle Valo void iwl_mvm_remove_time_event(struct iwl_mvm *mvm, 773e705c121SKalle Valo struct iwl_mvm_vif *mvmvif, 774e705c121SKalle Valo struct iwl_mvm_time_event_data *te_data) 775e705c121SKalle Valo { 776e705c121SKalle Valo struct iwl_time_event_cmd time_cmd = {}; 777e705c121SKalle Valo u32 uid; 778e705c121SKalle Valo int ret; 779e705c121SKalle Valo 780e705c121SKalle Valo if (!__iwl_mvm_remove_time_event(mvm, te_data, &uid)) 781e705c121SKalle Valo return; 782e705c121SKalle Valo 783e705c121SKalle Valo /* When we remove a TE, the UID is to be set in the id field */ 784e705c121SKalle Valo time_cmd.id = cpu_to_le32(uid); 785e705c121SKalle Valo time_cmd.action = cpu_to_le32(FW_CTXT_ACTION_REMOVE); 786e705c121SKalle Valo time_cmd.id_and_color = 787e705c121SKalle Valo cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, mvmvif->color)); 788e705c121SKalle Valo 789e705c121SKalle Valo IWL_DEBUG_TE(mvm, "Removing TE 0x%x\n", le32_to_cpu(time_cmd.id)); 790e705c121SKalle Valo ret = iwl_mvm_send_cmd_pdu(mvm, TIME_EVENT_CMD, 0, 791e705c121SKalle Valo sizeof(time_cmd), &time_cmd); 792997254a9SEmmanuel Grumbach if (ret) 793997254a9SEmmanuel Grumbach IWL_ERR(mvm, "Couldn't remove the time event\n"); 794e705c121SKalle Valo } 795e705c121SKalle Valo 796e705c121SKalle Valo void iwl_mvm_stop_session_protection(struct iwl_mvm *mvm, 797e705c121SKalle Valo struct ieee80211_vif *vif) 798e705c121SKalle Valo { 799e705c121SKalle Valo struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); 800e705c121SKalle Valo struct iwl_mvm_time_event_data *te_data = &mvmvif->time_event_data; 8013edfb5f4SAvraham Stern u32 id; 802e705c121SKalle Valo 803e705c121SKalle Valo lockdep_assert_held(&mvm->mutex); 8043edfb5f4SAvraham Stern 8053edfb5f4SAvraham Stern spin_lock_bh(&mvm->time_event_lock); 8063edfb5f4SAvraham Stern id = te_data->id; 8073edfb5f4SAvraham Stern spin_unlock_bh(&mvm->time_event_lock); 8083edfb5f4SAvraham Stern 8097b3954a1SIlan Peer if (fw_has_capa(&mvm->fw->ucode_capa, 8107b3954a1SIlan Peer IWL_UCODE_TLV_CAPA_SESSION_PROT_CMD)) { 8117b3954a1SIlan Peer if (id != SESSION_PROTECT_CONF_ASSOC) { 8127b3954a1SIlan Peer IWL_DEBUG_TE(mvm, 8137b3954a1SIlan Peer "don't remove session protection id=%u\n", 8147b3954a1SIlan Peer id); 8157b3954a1SIlan Peer return; 8167b3954a1SIlan Peer } 8177b3954a1SIlan Peer } else if (id != TE_BSS_STA_AGGRESSIVE_ASSOC) { 8183edfb5f4SAvraham Stern IWL_DEBUG_TE(mvm, 8193edfb5f4SAvraham Stern "don't remove TE with id=%u (not session protection)\n", 8203edfb5f4SAvraham Stern id); 8213edfb5f4SAvraham Stern return; 8223edfb5f4SAvraham Stern } 8233edfb5f4SAvraham Stern 824e705c121SKalle Valo iwl_mvm_remove_time_event(mvm, mvmvif, te_data); 825e705c121SKalle Valo } 826e705c121SKalle Valo 827fe959c7bSEmmanuel Grumbach void iwl_mvm_rx_session_protect_notif(struct iwl_mvm *mvm, 828fe959c7bSEmmanuel Grumbach struct iwl_rx_cmd_buffer *rxb) 829fe959c7bSEmmanuel Grumbach { 830fe959c7bSEmmanuel Grumbach struct iwl_rx_packet *pkt = rxb_addr(rxb); 831fe959c7bSEmmanuel Grumbach struct iwl_mvm_session_prot_notif *notif = (void *)pkt->data; 832fe959c7bSEmmanuel Grumbach struct ieee80211_vif *vif; 8331cf260e3SEmmanuel Grumbach struct iwl_mvm_vif *mvmvif; 834fe959c7bSEmmanuel Grumbach 835fe959c7bSEmmanuel Grumbach rcu_read_lock(); 836fe959c7bSEmmanuel Grumbach vif = iwl_mvm_rcu_dereference_vif_id(mvm, le32_to_cpu(notif->mac_id), 837fe959c7bSEmmanuel Grumbach true); 838fe959c7bSEmmanuel Grumbach 839fe959c7bSEmmanuel Grumbach if (!vif) 840fe959c7bSEmmanuel Grumbach goto out_unlock; 841fe959c7bSEmmanuel Grumbach 8421cf260e3SEmmanuel Grumbach mvmvif = iwl_mvm_vif_from_mac80211(vif); 8431cf260e3SEmmanuel Grumbach 844fe959c7bSEmmanuel Grumbach /* The vif is not a P2P_DEVICE, maintain its time_event_data */ 845fe959c7bSEmmanuel Grumbach if (vif->type != NL80211_IFTYPE_P2P_DEVICE) { 846fe959c7bSEmmanuel Grumbach struct iwl_mvm_time_event_data *te_data = 847fe959c7bSEmmanuel Grumbach &mvmvif->time_event_data; 848fe959c7bSEmmanuel Grumbach 849fe959c7bSEmmanuel Grumbach if (!le32_to_cpu(notif->status)) { 850fe959c7bSEmmanuel Grumbach iwl_mvm_te_check_disconnect(mvm, vif, 851fe959c7bSEmmanuel Grumbach "Session protection failure"); 852089e5016SAvraham Stern spin_lock_bh(&mvm->time_event_lock); 853fe959c7bSEmmanuel Grumbach iwl_mvm_te_clear_data(mvm, te_data); 854089e5016SAvraham Stern spin_unlock_bh(&mvm->time_event_lock); 855fe959c7bSEmmanuel Grumbach } 856fe959c7bSEmmanuel Grumbach 857fe959c7bSEmmanuel Grumbach if (le32_to_cpu(notif->start)) { 858fe959c7bSEmmanuel Grumbach spin_lock_bh(&mvm->time_event_lock); 859fe959c7bSEmmanuel Grumbach te_data->running = le32_to_cpu(notif->start); 860fe959c7bSEmmanuel Grumbach te_data->end_jiffies = 861fe959c7bSEmmanuel Grumbach TU_TO_EXP_TIME(te_data->duration); 862fe959c7bSEmmanuel Grumbach spin_unlock_bh(&mvm->time_event_lock); 863fe959c7bSEmmanuel Grumbach } else { 864fe959c7bSEmmanuel Grumbach /* 865fe959c7bSEmmanuel Grumbach * By now, we should have finished association 866fe959c7bSEmmanuel Grumbach * and know the dtim period. 867fe959c7bSEmmanuel Grumbach */ 868fe959c7bSEmmanuel Grumbach iwl_mvm_te_check_disconnect(mvm, vif, 869f276e20bSJohannes Berg !vif->cfg.assoc ? 870976ac0afSShaul Triebitz "Not associated and the session protection is over already..." : 871fe959c7bSEmmanuel Grumbach "No beacon heard and the session protection is over already..."); 872089e5016SAvraham Stern spin_lock_bh(&mvm->time_event_lock); 873fe959c7bSEmmanuel Grumbach iwl_mvm_te_clear_data(mvm, te_data); 874089e5016SAvraham Stern spin_unlock_bh(&mvm->time_event_lock); 875fe959c7bSEmmanuel Grumbach } 876fe959c7bSEmmanuel Grumbach 877fe959c7bSEmmanuel Grumbach goto out_unlock; 878fe959c7bSEmmanuel Grumbach } 879fe959c7bSEmmanuel Grumbach 880fe959c7bSEmmanuel Grumbach if (!le32_to_cpu(notif->status) || !le32_to_cpu(notif->start)) { 881fe959c7bSEmmanuel Grumbach /* End TE, notify mac80211 */ 8821cf260e3SEmmanuel Grumbach mvmvif->time_event_data.id = SESSION_PROTECT_CONF_MAX_ID; 883fe959c7bSEmmanuel Grumbach ieee80211_remain_on_channel_expired(mvm->hw); 884a76b5731SAvraham Stern iwl_mvm_p2p_roc_finished(mvm); 885fe959c7bSEmmanuel Grumbach } else if (le32_to_cpu(notif->start)) { 8861cf260e3SEmmanuel Grumbach if (WARN_ON(mvmvif->time_event_data.id != 8871cf260e3SEmmanuel Grumbach le32_to_cpu(notif->conf_id))) 8881cf260e3SEmmanuel Grumbach goto out_unlock; 889fe959c7bSEmmanuel Grumbach set_bit(IWL_MVM_STATUS_ROC_RUNNING, &mvm->status); 890fe959c7bSEmmanuel Grumbach ieee80211_ready_on_channel(mvm->hw); /* Start TE */ 891fe959c7bSEmmanuel Grumbach } 892fe959c7bSEmmanuel Grumbach 893fe959c7bSEmmanuel Grumbach out_unlock: 894fe959c7bSEmmanuel Grumbach rcu_read_unlock(); 895fe959c7bSEmmanuel Grumbach } 896fe959c7bSEmmanuel Grumbach 897fe959c7bSEmmanuel Grumbach static int 898fe959c7bSEmmanuel Grumbach iwl_mvm_start_p2p_roc_session_protection(struct iwl_mvm *mvm, 899fe959c7bSEmmanuel Grumbach struct ieee80211_vif *vif, 900fe959c7bSEmmanuel Grumbach int duration, 901fe959c7bSEmmanuel Grumbach enum ieee80211_roc_type type) 902fe959c7bSEmmanuel Grumbach { 903fe959c7bSEmmanuel Grumbach struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); 904fe959c7bSEmmanuel Grumbach struct iwl_mvm_session_prot_cmd cmd = { 905fe959c7bSEmmanuel Grumbach .id_and_color = 906fe959c7bSEmmanuel Grumbach cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, 907fe959c7bSEmmanuel Grumbach mvmvif->color)), 908fe959c7bSEmmanuel Grumbach .action = cpu_to_le32(FW_CTXT_ACTION_ADD), 909fe959c7bSEmmanuel Grumbach .duration_tu = cpu_to_le32(MSEC_TO_TU(duration)), 910fe959c7bSEmmanuel Grumbach }; 911fe959c7bSEmmanuel Grumbach 912fe959c7bSEmmanuel Grumbach lockdep_assert_held(&mvm->mutex); 913fe959c7bSEmmanuel Grumbach 9141cf260e3SEmmanuel Grumbach /* The time_event_data.id field is reused to save session 9151cf260e3SEmmanuel Grumbach * protection's configuration. 9161cf260e3SEmmanuel Grumbach */ 917fe959c7bSEmmanuel Grumbach switch (type) { 918fe959c7bSEmmanuel Grumbach case IEEE80211_ROC_TYPE_NORMAL: 9191cf260e3SEmmanuel Grumbach mvmvif->time_event_data.id = 9201cf260e3SEmmanuel Grumbach SESSION_PROTECT_CONF_P2P_DEVICE_DISCOV; 921fe959c7bSEmmanuel Grumbach break; 922fe959c7bSEmmanuel Grumbach case IEEE80211_ROC_TYPE_MGMT_TX: 9231cf260e3SEmmanuel Grumbach mvmvif->time_event_data.id = 9241cf260e3SEmmanuel Grumbach SESSION_PROTECT_CONF_P2P_GO_NEGOTIATION; 925fe959c7bSEmmanuel Grumbach break; 926fe959c7bSEmmanuel Grumbach default: 927fe959c7bSEmmanuel Grumbach WARN_ONCE(1, "Got an invalid ROC type\n"); 928fe959c7bSEmmanuel Grumbach return -EINVAL; 929fe959c7bSEmmanuel Grumbach } 930fe959c7bSEmmanuel Grumbach 9311cf260e3SEmmanuel Grumbach cmd.conf_id = cpu_to_le32(mvmvif->time_event_data.id); 932f0c86427SJohannes Berg return iwl_mvm_send_cmd_pdu(mvm, 933f0c86427SJohannes Berg WIDE_ID(MAC_CONF_GROUP, SESSION_PROTECTION_CMD), 934fe959c7bSEmmanuel Grumbach 0, sizeof(cmd), &cmd); 935fe959c7bSEmmanuel Grumbach } 936fe959c7bSEmmanuel Grumbach 937e705c121SKalle Valo int iwl_mvm_start_p2p_roc(struct iwl_mvm *mvm, struct ieee80211_vif *vif, 938e705c121SKalle Valo int duration, enum ieee80211_roc_type type) 939e705c121SKalle Valo { 940e705c121SKalle Valo struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); 941e705c121SKalle Valo struct iwl_mvm_time_event_data *te_data = &mvmvif->time_event_data; 942e705c121SKalle Valo struct iwl_time_event_cmd time_cmd = {}; 943e705c121SKalle Valo 944e705c121SKalle Valo lockdep_assert_held(&mvm->mutex); 945e705c121SKalle Valo if (te_data->running) { 946e705c121SKalle Valo IWL_WARN(mvm, "P2P_DEVICE remain on channel already running\n"); 947e705c121SKalle Valo return -EBUSY; 948e705c121SKalle Valo } 949e705c121SKalle Valo 950fe959c7bSEmmanuel Grumbach if (fw_has_capa(&mvm->fw->ucode_capa, 951fe959c7bSEmmanuel Grumbach IWL_UCODE_TLV_CAPA_SESSION_PROT_CMD)) 952fe959c7bSEmmanuel Grumbach return iwl_mvm_start_p2p_roc_session_protection(mvm, vif, 953fe959c7bSEmmanuel Grumbach duration, 954fe959c7bSEmmanuel Grumbach type); 955fe959c7bSEmmanuel Grumbach 956e705c121SKalle Valo time_cmd.action = cpu_to_le32(FW_CTXT_ACTION_ADD); 957e705c121SKalle Valo time_cmd.id_and_color = 958e705c121SKalle Valo cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, mvmvif->color)); 959e705c121SKalle Valo 960e705c121SKalle Valo switch (type) { 961e705c121SKalle Valo case IEEE80211_ROC_TYPE_NORMAL: 962e705c121SKalle Valo time_cmd.id = cpu_to_le32(IWL_MVM_ROC_TE_TYPE_NORMAL); 963e705c121SKalle Valo break; 964e705c121SKalle Valo case IEEE80211_ROC_TYPE_MGMT_TX: 965e705c121SKalle Valo time_cmd.id = cpu_to_le32(IWL_MVM_ROC_TE_TYPE_MGMT_TX); 966e705c121SKalle Valo break; 967e705c121SKalle Valo default: 968e705c121SKalle Valo WARN_ONCE(1, "Got an invalid ROC type\n"); 969e705c121SKalle Valo return -EINVAL; 970e705c121SKalle Valo } 971e705c121SKalle Valo 972e705c121SKalle Valo time_cmd.apply_time = cpu_to_le32(0); 973e705c121SKalle Valo time_cmd.interval = cpu_to_le32(1); 974e705c121SKalle Valo 975e705c121SKalle Valo /* 976e705c121SKalle Valo * The P2P Device TEs can have lower priority than other events 977e705c121SKalle Valo * that are being scheduled by the driver/fw, and thus it might not be 978e705c121SKalle Valo * scheduled. To improve the chances of it being scheduled, allow them 979e705c121SKalle Valo * to be fragmented, and in addition allow them to be delayed. 980e705c121SKalle Valo */ 981e705c121SKalle Valo time_cmd.max_frags = min(MSEC_TO_TU(duration)/50, TE_V2_FRAG_ENDLESS); 982e705c121SKalle Valo time_cmd.max_delay = cpu_to_le32(MSEC_TO_TU(duration/2)); 983e705c121SKalle Valo time_cmd.duration = cpu_to_le32(MSEC_TO_TU(duration)); 984e705c121SKalle Valo time_cmd.repeat = 1; 985e705c121SKalle Valo time_cmd.policy = cpu_to_le16(TE_V2_NOTIF_HOST_EVENT_START | 986e705c121SKalle Valo TE_V2_NOTIF_HOST_EVENT_END | 98740d53f4aSAndrei Otcheretianski TE_V2_START_IMMEDIATELY); 988e705c121SKalle Valo 989e705c121SKalle Valo return iwl_mvm_time_event_send_add(mvm, vif, te_data, &time_cmd); 990e705c121SKalle Valo } 991e705c121SKalle Valo 992305d236eSEliad Peller static struct iwl_mvm_time_event_data *iwl_mvm_get_roc_te(struct iwl_mvm *mvm) 993e705c121SKalle Valo { 994e705c121SKalle Valo struct iwl_mvm_time_event_data *te_data; 995e705c121SKalle Valo 996e705c121SKalle Valo lockdep_assert_held(&mvm->mutex); 997e705c121SKalle Valo 998e705c121SKalle Valo spin_lock_bh(&mvm->time_event_lock); 999e705c121SKalle Valo 1000e705c121SKalle Valo /* 1001e705c121SKalle Valo * Iterate over the list of time events and find the time event that is 1002e705c121SKalle Valo * associated with a P2P_DEVICE interface. 1003e705c121SKalle Valo * This assumes that a P2P_DEVICE interface can have only a single time 1004e705c121SKalle Valo * event at any given time and this time event coresponds to a ROC 1005e705c121SKalle Valo * request 1006e705c121SKalle Valo */ 1007e705c121SKalle Valo list_for_each_entry(te_data, &mvm->time_event_list, list) { 1008305d236eSEliad Peller if (te_data->vif->type == NL80211_IFTYPE_P2P_DEVICE) 1009305d236eSEliad Peller goto out; 1010e705c121SKalle Valo } 1011e705c121SKalle Valo 1012e705c121SKalle Valo /* There can only be at most one AUX ROC time event, we just use the 1013e705c121SKalle Valo * list to simplify/unify code. Remove it if it exists. 1014e705c121SKalle Valo */ 1015e705c121SKalle Valo te_data = list_first_entry_or_null(&mvm->aux_roc_te_list, 1016e705c121SKalle Valo struct iwl_mvm_time_event_data, 1017e705c121SKalle Valo list); 1018305d236eSEliad Peller out: 1019e705c121SKalle Valo spin_unlock_bh(&mvm->time_event_lock); 1020305d236eSEliad Peller return te_data; 1021305d236eSEliad Peller } 1022e705c121SKalle Valo 1023305d236eSEliad Peller void iwl_mvm_cleanup_roc_te(struct iwl_mvm *mvm) 1024305d236eSEliad Peller { 1025305d236eSEliad Peller struct iwl_mvm_time_event_data *te_data; 1026305d236eSEliad Peller u32 uid; 1027305d236eSEliad Peller 1028305d236eSEliad Peller te_data = iwl_mvm_get_roc_te(mvm); 1029305d236eSEliad Peller if (te_data) 1030305d236eSEliad Peller __iwl_mvm_remove_time_event(mvm, te_data, &uid); 1031305d236eSEliad Peller } 1032305d236eSEliad Peller 1033fe959c7bSEmmanuel Grumbach void iwl_mvm_stop_roc(struct iwl_mvm *mvm, struct ieee80211_vif *vif) 1034305d236eSEliad Peller { 1035305d236eSEliad Peller struct iwl_mvm_vif *mvmvif; 1036305d236eSEliad Peller struct iwl_mvm_time_event_data *te_data; 1037305d236eSEliad Peller 1038fe959c7bSEmmanuel Grumbach if (fw_has_capa(&mvm->fw->ucode_capa, 1039fe959c7bSEmmanuel Grumbach IWL_UCODE_TLV_CAPA_SESSION_PROT_CMD)) { 1040fe959c7bSEmmanuel Grumbach mvmvif = iwl_mvm_vif_from_mac80211(vif); 1041fe959c7bSEmmanuel Grumbach 1042fb8d1b6eSEmmanuel Grumbach if (vif->type == NL80211_IFTYPE_P2P_DEVICE) { 10437b3954a1SIlan Peer iwl_mvm_cancel_session_protection(mvm, mvmvif, 10447b3954a1SIlan Peer mvmvif->time_event_data.id); 1045a76b5731SAvraham Stern iwl_mvm_p2p_roc_finished(mvm); 1046fb8d1b6eSEmmanuel Grumbach } else { 1047fb8d1b6eSEmmanuel Grumbach iwl_mvm_remove_aux_roc_te(mvm, mvmvif, 1048f0337cb4SAvraham Stern &mvmvif->hs_time_event_data); 1049fe959c7bSEmmanuel Grumbach iwl_mvm_roc_finished(mvm); 1050a76b5731SAvraham Stern } 1051fe959c7bSEmmanuel Grumbach 1052fe959c7bSEmmanuel Grumbach return; 1053fe959c7bSEmmanuel Grumbach } 1054fe959c7bSEmmanuel Grumbach 1055305d236eSEliad Peller te_data = iwl_mvm_get_roc_te(mvm); 1056305d236eSEliad Peller if (!te_data) { 1057e705c121SKalle Valo IWL_WARN(mvm, "No remain on channel event\n"); 1058e705c121SKalle Valo return; 1059e705c121SKalle Valo } 1060e705c121SKalle Valo 1061305d236eSEliad Peller mvmvif = iwl_mvm_vif_from_mac80211(te_data->vif); 1062305d236eSEliad Peller 10636c2d49fdSJohannes Berg if (te_data->vif->type == NL80211_IFTYPE_P2P_DEVICE) { 1064e705c121SKalle Valo iwl_mvm_remove_time_event(mvm, mvmvif, te_data); 1065a76b5731SAvraham Stern iwl_mvm_p2p_roc_finished(mvm); 10666c2d49fdSJohannes Berg } else { 1067e705c121SKalle Valo iwl_mvm_remove_aux_roc_te(mvm, mvmvif, te_data); 1068e705c121SKalle Valo iwl_mvm_roc_finished(mvm); 1069e705c121SKalle Valo } 1070a76b5731SAvraham Stern } 1071e705c121SKalle Valo 107258ddd9b6SEmmanuel Grumbach void iwl_mvm_remove_csa_period(struct iwl_mvm *mvm, 107358ddd9b6SEmmanuel Grumbach struct ieee80211_vif *vif) 107458ddd9b6SEmmanuel Grumbach { 107558ddd9b6SEmmanuel Grumbach struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); 107658ddd9b6SEmmanuel Grumbach struct iwl_mvm_time_event_data *te_data = &mvmvif->time_event_data; 107758ddd9b6SEmmanuel Grumbach u32 id; 107858ddd9b6SEmmanuel Grumbach 107958ddd9b6SEmmanuel Grumbach lockdep_assert_held(&mvm->mutex); 108058ddd9b6SEmmanuel Grumbach 108158ddd9b6SEmmanuel Grumbach spin_lock_bh(&mvm->time_event_lock); 108258ddd9b6SEmmanuel Grumbach id = te_data->id; 108358ddd9b6SEmmanuel Grumbach spin_unlock_bh(&mvm->time_event_lock); 108458ddd9b6SEmmanuel Grumbach 108558ddd9b6SEmmanuel Grumbach if (id != TE_CHANNEL_SWITCH_PERIOD) 108658ddd9b6SEmmanuel Grumbach return; 108758ddd9b6SEmmanuel Grumbach 108858ddd9b6SEmmanuel Grumbach iwl_mvm_remove_time_event(mvm, mvmvif, te_data); 108958ddd9b6SEmmanuel Grumbach } 109058ddd9b6SEmmanuel Grumbach 1091e705c121SKalle Valo int iwl_mvm_schedule_csa_period(struct iwl_mvm *mvm, 1092e705c121SKalle Valo struct ieee80211_vif *vif, 1093e705c121SKalle Valo u32 duration, u32 apply_time) 1094e705c121SKalle Valo { 1095e705c121SKalle Valo struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); 1096e705c121SKalle Valo struct iwl_mvm_time_event_data *te_data = &mvmvif->time_event_data; 1097e705c121SKalle Valo struct iwl_time_event_cmd time_cmd = {}; 1098e705c121SKalle Valo 1099e705c121SKalle Valo lockdep_assert_held(&mvm->mutex); 1100e705c121SKalle Valo 1101e705c121SKalle Valo if (te_data->running) { 11023edfb5f4SAvraham Stern u32 id; 11033edfb5f4SAvraham Stern 11043edfb5f4SAvraham Stern spin_lock_bh(&mvm->time_event_lock); 11053edfb5f4SAvraham Stern id = te_data->id; 11063edfb5f4SAvraham Stern spin_unlock_bh(&mvm->time_event_lock); 11073edfb5f4SAvraham Stern 11083edfb5f4SAvraham Stern if (id == TE_CHANNEL_SWITCH_PERIOD) { 1109e705c121SKalle Valo IWL_DEBUG_TE(mvm, "CS period is already scheduled\n"); 1110e705c121SKalle Valo return -EBUSY; 1111e705c121SKalle Valo } 1112e705c121SKalle Valo 11133edfb5f4SAvraham Stern /* 11143edfb5f4SAvraham Stern * Remove the session protection time event to allow the 11153edfb5f4SAvraham Stern * channel switch. If we got here, we just heard a beacon so 11163edfb5f4SAvraham Stern * the session protection is not needed anymore anyway. 11173edfb5f4SAvraham Stern */ 11183edfb5f4SAvraham Stern iwl_mvm_remove_time_event(mvm, mvmvif, te_data); 11193edfb5f4SAvraham Stern } 11203edfb5f4SAvraham Stern 1121e705c121SKalle Valo time_cmd.action = cpu_to_le32(FW_CTXT_ACTION_ADD); 1122e705c121SKalle Valo time_cmd.id_and_color = 1123e705c121SKalle Valo cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, mvmvif->color)); 1124e705c121SKalle Valo time_cmd.id = cpu_to_le32(TE_CHANNEL_SWITCH_PERIOD); 1125e705c121SKalle Valo time_cmd.apply_time = cpu_to_le32(apply_time); 1126e705c121SKalle Valo time_cmd.max_frags = TE_V2_FRAG_NONE; 1127e705c121SKalle Valo time_cmd.duration = cpu_to_le32(duration); 1128e705c121SKalle Valo time_cmd.repeat = 1; 1129e705c121SKalle Valo time_cmd.interval = cpu_to_le32(1); 1130e705c121SKalle Valo time_cmd.policy = cpu_to_le16(TE_V2_NOTIF_HOST_EVENT_START | 1131e705c121SKalle Valo TE_V2_ABSENCE); 113240d53f4aSAndrei Otcheretianski if (!apply_time) 113340d53f4aSAndrei Otcheretianski time_cmd.policy |= cpu_to_le16(TE_V2_START_IMMEDIATELY); 1134e705c121SKalle Valo 1135e705c121SKalle Valo return iwl_mvm_time_event_send_add(mvm, vif, te_data, &time_cmd); 1136e705c121SKalle Valo } 1137fe959c7bSEmmanuel Grumbach 1138b5b878e3SEmmanuel Grumbach static bool iwl_mvm_session_prot_notif(struct iwl_notif_wait_data *notif_wait, 1139b5b878e3SEmmanuel Grumbach struct iwl_rx_packet *pkt, void *data) 1140b5b878e3SEmmanuel Grumbach { 1141b5b878e3SEmmanuel Grumbach struct iwl_mvm *mvm = 1142b5b878e3SEmmanuel Grumbach container_of(notif_wait, struct iwl_mvm, notif_wait); 1143b5b878e3SEmmanuel Grumbach struct iwl_mvm_session_prot_notif *resp; 1144b5b878e3SEmmanuel Grumbach int resp_len = iwl_rx_packet_payload_len(pkt); 1145b5b878e3SEmmanuel Grumbach 1146b5b878e3SEmmanuel Grumbach if (WARN_ON(pkt->hdr.cmd != SESSION_PROTECTION_NOTIF || 1147b5b878e3SEmmanuel Grumbach pkt->hdr.group_id != MAC_CONF_GROUP)) 1148b5b878e3SEmmanuel Grumbach return true; 1149b5b878e3SEmmanuel Grumbach 1150b5b878e3SEmmanuel Grumbach if (WARN_ON_ONCE(resp_len != sizeof(*resp))) { 1151b5b878e3SEmmanuel Grumbach IWL_ERR(mvm, "Invalid SESSION_PROTECTION_NOTIF response\n"); 1152b5b878e3SEmmanuel Grumbach return true; 1153b5b878e3SEmmanuel Grumbach } 1154b5b878e3SEmmanuel Grumbach 1155b5b878e3SEmmanuel Grumbach resp = (void *)pkt->data; 1156b5b878e3SEmmanuel Grumbach 1157b5b878e3SEmmanuel Grumbach if (!resp->status) 1158b5b878e3SEmmanuel Grumbach IWL_ERR(mvm, 1159b5b878e3SEmmanuel Grumbach "TIME_EVENT_NOTIFICATION received but not executed\n"); 1160b5b878e3SEmmanuel Grumbach 1161b5b878e3SEmmanuel Grumbach return true; 1162b5b878e3SEmmanuel Grumbach } 1163b5b878e3SEmmanuel Grumbach 1164fe959c7bSEmmanuel Grumbach void iwl_mvm_schedule_session_protection(struct iwl_mvm *mvm, 1165fe959c7bSEmmanuel Grumbach struct ieee80211_vif *vif, 1166b5b878e3SEmmanuel Grumbach u32 duration, u32 min_duration, 1167b5b878e3SEmmanuel Grumbach bool wait_for_notif) 1168fe959c7bSEmmanuel Grumbach { 1169fe959c7bSEmmanuel Grumbach struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); 1170fe959c7bSEmmanuel Grumbach struct iwl_mvm_time_event_data *te_data = &mvmvif->time_event_data; 1171f0c86427SJohannes Berg const u16 notif[] = { WIDE_ID(MAC_CONF_GROUP, SESSION_PROTECTION_NOTIF) }; 1172b5b878e3SEmmanuel Grumbach struct iwl_notification_wait wait_notif; 1173fe959c7bSEmmanuel Grumbach struct iwl_mvm_session_prot_cmd cmd = { 1174fe959c7bSEmmanuel Grumbach .id_and_color = 1175fe959c7bSEmmanuel Grumbach cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, 1176fe959c7bSEmmanuel Grumbach mvmvif->color)), 1177fe959c7bSEmmanuel Grumbach .action = cpu_to_le32(FW_CTXT_ACTION_ADD), 11788e967c13SShaul Triebitz .conf_id = cpu_to_le32(SESSION_PROTECT_CONF_ASSOC), 1179fe959c7bSEmmanuel Grumbach .duration_tu = cpu_to_le32(MSEC_TO_TU(duration)), 1180fe959c7bSEmmanuel Grumbach }; 1181fe959c7bSEmmanuel Grumbach 1182fe959c7bSEmmanuel Grumbach lockdep_assert_held(&mvm->mutex); 1183fe959c7bSEmmanuel Grumbach 1184fe959c7bSEmmanuel Grumbach spin_lock_bh(&mvm->time_event_lock); 1185fe959c7bSEmmanuel Grumbach if (te_data->running && 1186fe959c7bSEmmanuel Grumbach time_after(te_data->end_jiffies, TU_TO_EXP_TIME(min_duration))) { 1187fe959c7bSEmmanuel Grumbach IWL_DEBUG_TE(mvm, "We have enough time in the current TE: %u\n", 1188fe959c7bSEmmanuel Grumbach jiffies_to_msecs(te_data->end_jiffies - jiffies)); 1189fe959c7bSEmmanuel Grumbach spin_unlock_bh(&mvm->time_event_lock); 1190fe959c7bSEmmanuel Grumbach 1191fe959c7bSEmmanuel Grumbach return; 1192fe959c7bSEmmanuel Grumbach } 1193fe959c7bSEmmanuel Grumbach 1194fe959c7bSEmmanuel Grumbach iwl_mvm_te_clear_data(mvm, te_data); 11958e967c13SShaul Triebitz /* 11968e967c13SShaul Triebitz * The time_event_data.id field is reused to save session 11978e967c13SShaul Triebitz * protection's configuration. 11988e967c13SShaul Triebitz */ 11998e967c13SShaul Triebitz te_data->id = le32_to_cpu(cmd.conf_id); 1200fe959c7bSEmmanuel Grumbach te_data->duration = le32_to_cpu(cmd.duration_tu); 12017b3954a1SIlan Peer te_data->vif = vif; 1202fe959c7bSEmmanuel Grumbach spin_unlock_bh(&mvm->time_event_lock); 1203fe959c7bSEmmanuel Grumbach 1204fe959c7bSEmmanuel Grumbach IWL_DEBUG_TE(mvm, "Add new session protection, duration %d TU\n", 1205fe959c7bSEmmanuel Grumbach le32_to_cpu(cmd.duration_tu)); 1206fe959c7bSEmmanuel Grumbach 1207b5b878e3SEmmanuel Grumbach if (!wait_for_notif) { 1208b5b878e3SEmmanuel Grumbach if (iwl_mvm_send_cmd_pdu(mvm, 1209f0c86427SJohannes Berg WIDE_ID(MAC_CONF_GROUP, SESSION_PROTECTION_CMD), 1210b5b878e3SEmmanuel Grumbach 0, sizeof(cmd), &cmd)) { 1211fe959c7bSEmmanuel Grumbach IWL_ERR(mvm, 1212b5b878e3SEmmanuel Grumbach "Couldn't send the SESSION_PROTECTION_CMD\n"); 1213fe959c7bSEmmanuel Grumbach spin_lock_bh(&mvm->time_event_lock); 1214fe959c7bSEmmanuel Grumbach iwl_mvm_te_clear_data(mvm, te_data); 1215fe959c7bSEmmanuel Grumbach spin_unlock_bh(&mvm->time_event_lock); 1216fe959c7bSEmmanuel Grumbach } 1217b5b878e3SEmmanuel Grumbach 1218b5b878e3SEmmanuel Grumbach return; 1219b5b878e3SEmmanuel Grumbach } 1220b5b878e3SEmmanuel Grumbach 1221b5b878e3SEmmanuel Grumbach iwl_init_notification_wait(&mvm->notif_wait, &wait_notif, 1222b5b878e3SEmmanuel Grumbach notif, ARRAY_SIZE(notif), 1223b5b878e3SEmmanuel Grumbach iwl_mvm_session_prot_notif, NULL); 1224b5b878e3SEmmanuel Grumbach 1225b5b878e3SEmmanuel Grumbach if (iwl_mvm_send_cmd_pdu(mvm, 1226f0c86427SJohannes Berg WIDE_ID(MAC_CONF_GROUP, SESSION_PROTECTION_CMD), 1227b5b878e3SEmmanuel Grumbach 0, sizeof(cmd), &cmd)) { 1228b5b878e3SEmmanuel Grumbach IWL_ERR(mvm, 1229b5b878e3SEmmanuel Grumbach "Couldn't send the SESSION_PROTECTION_CMD\n"); 1230b5b878e3SEmmanuel Grumbach iwl_remove_notification(&mvm->notif_wait, &wait_notif); 1231b5b878e3SEmmanuel Grumbach } else if (iwl_wait_notification(&mvm->notif_wait, &wait_notif, 1232b5b878e3SEmmanuel Grumbach TU_TO_JIFFIES(100))) { 1233b5b878e3SEmmanuel Grumbach IWL_ERR(mvm, 1234b5b878e3SEmmanuel Grumbach "Failed to protect session until session protection\n"); 1235b5b878e3SEmmanuel Grumbach } 1236fe959c7bSEmmanuel Grumbach } 1237