// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* * Copyright (C) 2024-2025 Intel Corporation */ #include #include #include "fw/api/rx.h" #include "fw/api/datapath.h" #include "fw/api/commands.h" #include "fw/api/offload.h" #include "fw/api/coex.h" #include "fw/dbg.h" #include "fw/uefi.h" #include "mld.h" #include "mlo.h" #include "mac80211.h" #include "led.h" #include "scan.h" #include "tx.h" #include "sta.h" #include "regulatory.h" #include "thermal.h" #include "low_latency.h" #include "hcmd.h" #include "fw/api/location.h" #define DRV_DESCRIPTION "Intel(R) MLD wireless driver for Linux" MODULE_DESCRIPTION(DRV_DESCRIPTION); MODULE_LICENSE("GPL"); MODULE_IMPORT_NS("IWLWIFI"); static const struct iwl_op_mode_ops iwl_mld_ops; static int __init iwl_mld_init(void) { int ret = iwl_opmode_register("iwlmld", &iwl_mld_ops); if (ret) pr_err("Unable to register MLD op_mode: %d\n", ret); return ret; } module_init(iwl_mld_init); static void __exit iwl_mld_exit(void) { iwl_opmode_deregister("iwlmld"); } module_exit(iwl_mld_exit); static void iwl_mld_hw_set_regulatory(struct iwl_mld *mld) { struct wiphy *wiphy = mld->wiphy; wiphy->regulatory_flags |= REGULATORY_WIPHY_SELF_MANAGED; wiphy->regulatory_flags |= REGULATORY_ENABLE_RELAX_NO_IR; } VISIBLE_IF_IWLWIFI_KUNIT void iwl_construct_mld(struct iwl_mld *mld, struct iwl_trans *trans, const struct iwl_cfg *cfg, const struct iwl_fw *fw, struct ieee80211_hw *hw, struct dentry *dbgfs_dir) { mld->dev = trans->dev; mld->trans = trans; mld->cfg = cfg; mld->fw = fw; mld->hw = hw; mld->wiphy = hw->wiphy; mld->debugfs_dir = dbgfs_dir; iwl_notification_wait_init(&mld->notif_wait); /* Setup async RX handling */ spin_lock_init(&mld->async_handlers_lock); wiphy_work_init(&mld->async_handlers_wk, iwl_mld_async_handlers_wk); /* Dynamic Queue Allocation */ spin_lock_init(&mld->add_txqs_lock); INIT_LIST_HEAD(&mld->txqs_to_add); wiphy_work_init(&mld->add_txqs_wk, iwl_mld_add_txqs_wk); /* Setup RX queues sync wait queue */ init_waitqueue_head(&mld->rxq_sync.waitq); } EXPORT_SYMBOL_IF_IWLWIFI_KUNIT(iwl_construct_mld); static void __acquires(&mld->wiphy->mtx) iwl_mld_fwrt_dump_start(void *ctx) { struct iwl_mld *mld = ctx; wiphy_lock(mld->wiphy); } static void __releases(&mld->wiphy->mtx) iwl_mld_fwrt_dump_end(void *ctx) { struct iwl_mld *mld = ctx; wiphy_unlock(mld->wiphy); } static bool iwl_mld_d3_debug_enable(void *ctx) { return IWL_MLD_D3_DEBUG; } static int iwl_mld_fwrt_send_hcmd(void *ctx, struct iwl_host_cmd *host_cmd) { struct iwl_mld *mld = (struct iwl_mld *)ctx; int ret; wiphy_lock(mld->wiphy); ret = iwl_mld_send_cmd(mld, host_cmd); wiphy_unlock(mld->wiphy); return ret; } static const struct iwl_fw_runtime_ops iwl_mld_fwrt_ops = { .dump_start = iwl_mld_fwrt_dump_start, .dump_end = iwl_mld_fwrt_dump_end, .send_hcmd = iwl_mld_fwrt_send_hcmd, .d3_debug_enable = iwl_mld_d3_debug_enable, }; static void iwl_mld_construct_fw_runtime(struct iwl_mld *mld, struct iwl_trans *trans, const struct iwl_fw *fw, struct dentry *debugfs_dir) { iwl_fw_runtime_init(&mld->fwrt, trans, fw, &iwl_mld_fwrt_ops, mld, NULL, NULL, debugfs_dir); iwl_fw_set_current_image(&mld->fwrt, IWL_UCODE_REGULAR); } /* Please keep this array *SORTED* by hex value. * Access is done through binary search */ static const struct iwl_hcmd_names iwl_mld_legacy_names[] = { HCMD_NAME(UCODE_ALIVE_NTFY), HCMD_NAME(INIT_COMPLETE_NOTIF), HCMD_NAME(PHY_CONTEXT_CMD), HCMD_NAME(SCAN_CFG_CMD), HCMD_NAME(SCAN_REQ_UMAC), HCMD_NAME(SCAN_ABORT_UMAC), HCMD_NAME(SCAN_COMPLETE_UMAC), HCMD_NAME(TX_CMD), HCMD_NAME(TXPATH_FLUSH), HCMD_NAME(LEDS_CMD), HCMD_NAME(WNM_80211V_TIMING_MEASUREMENT_NOTIFICATION), HCMD_NAME(WNM_80211V_TIMING_MEASUREMENT_CONFIRM_NOTIFICATION), HCMD_NAME(SCAN_OFFLOAD_UPDATE_PROFILES_CMD), HCMD_NAME(POWER_TABLE_CMD), HCMD_NAME(PSM_UAPSD_AP_MISBEHAVING_NOTIFICATION), HCMD_NAME(BEACON_NOTIFICATION), HCMD_NAME(BEACON_TEMPLATE_CMD), HCMD_NAME(TX_ANT_CONFIGURATION_CMD), HCMD_NAME(REDUCE_TX_POWER_CMD), HCMD_NAME(MISSED_BEACONS_NOTIFICATION), HCMD_NAME(MAC_PM_POWER_TABLE), HCMD_NAME(MFUART_LOAD_NOTIFICATION), HCMD_NAME(RSS_CONFIG_CMD), HCMD_NAME(SCAN_ITERATION_COMPLETE_UMAC), HCMD_NAME(REPLY_RX_MPDU_CMD), HCMD_NAME(BA_NOTIF), HCMD_NAME(MCC_UPDATE_CMD), HCMD_NAME(MCC_CHUB_UPDATE_CMD), HCMD_NAME(MCAST_FILTER_CMD), HCMD_NAME(REPLY_BEACON_FILTERING_CMD), HCMD_NAME(PROT_OFFLOAD_CONFIG_CMD), HCMD_NAME(MATCH_FOUND_NOTIFICATION), HCMD_NAME(WOWLAN_PATTERNS), HCMD_NAME(WOWLAN_CONFIGURATION), HCMD_NAME(WOWLAN_TSC_RSC_PARAM), HCMD_NAME(WOWLAN_KEK_KCK_MATERIAL), HCMD_NAME(DEBUG_HOST_COMMAND), HCMD_NAME(LDBG_CONFIG_CMD), }; /* Please keep this array *SORTED* by hex value. * Access is done through binary search */ static const struct iwl_hcmd_names iwl_mld_system_names[] = { HCMD_NAME(SHARED_MEM_CFG_CMD), HCMD_NAME(SOC_CONFIGURATION_CMD), HCMD_NAME(INIT_EXTENDED_CFG_CMD), HCMD_NAME(FW_ERROR_RECOVERY_CMD), HCMD_NAME(RFI_GET_FREQ_TABLE_CMD), HCMD_NAME(SYSTEM_STATISTICS_CMD), HCMD_NAME(SYSTEM_STATISTICS_END_NOTIF), }; /* Please keep this array *SORTED* by hex value. * Access is done through binary search */ static const struct iwl_hcmd_names iwl_mld_reg_and_nvm_names[] = { HCMD_NAME(LARI_CONFIG_CHANGE), HCMD_NAME(NVM_GET_INFO), HCMD_NAME(TAS_CONFIG), HCMD_NAME(SAR_OFFSET_MAPPING_TABLE_CMD), HCMD_NAME(MCC_ALLOWED_AP_TYPE_CMD), }; /* Please keep this array *SORTED* by hex value. * Access is done through binary search */ static const struct iwl_hcmd_names iwl_mld_debug_names[] = { HCMD_NAME(HOST_EVENT_CFG), HCMD_NAME(DBGC_SUSPEND_RESUME), }; /* Please keep this array *SORTED* by hex value. * Access is done through binary search */ static const struct iwl_hcmd_names iwl_mld_mac_conf_names[] = { HCMD_NAME(LOW_LATENCY_CMD), HCMD_NAME(SESSION_PROTECTION_CMD), HCMD_NAME(MAC_CONFIG_CMD), HCMD_NAME(LINK_CONFIG_CMD), HCMD_NAME(STA_CONFIG_CMD), HCMD_NAME(AUX_STA_CMD), HCMD_NAME(STA_REMOVE_CMD), HCMD_NAME(ROC_CMD), HCMD_NAME(MISSED_BEACONS_NOTIF), HCMD_NAME(EMLSR_TRANS_FAIL_NOTIF), HCMD_NAME(ROC_NOTIF), HCMD_NAME(CHANNEL_SWITCH_ERROR_NOTIF), HCMD_NAME(SESSION_PROTECTION_NOTIF), HCMD_NAME(PROBE_RESPONSE_DATA_NOTIF), HCMD_NAME(CHANNEL_SWITCH_START_NOTIF), }; /* Please keep this array *SORTED* by hex value. * Access is done through binary search */ static const struct iwl_hcmd_names iwl_mld_data_path_names[] = { HCMD_NAME(TRIGGER_RX_QUEUES_NOTIF_CMD), HCMD_NAME(WNM_PLATFORM_PTM_REQUEST_CMD), HCMD_NAME(WNM_80211V_TIMING_MEASUREMENT_CONFIG_CMD), HCMD_NAME(RFH_QUEUE_CONFIG_CMD), HCMD_NAME(TLC_MNG_CONFIG_CMD), HCMD_NAME(RX_BAID_ALLOCATION_CONFIG_CMD), HCMD_NAME(SCD_QUEUE_CONFIG_CMD), HCMD_NAME(OMI_SEND_STATUS_NOTIF), HCMD_NAME(ESR_MODE_NOTIF), HCMD_NAME(MONITOR_NOTIF), HCMD_NAME(TLC_MNG_UPDATE_NOTIF), HCMD_NAME(MU_GROUP_MGMT_NOTIF), }; /* Please keep this array *SORTED* by hex value. * Access is done through binary search */ static const struct iwl_hcmd_names iwl_mld_location_names[] = { HCMD_NAME(TOF_RANGE_REQ_CMD), HCMD_NAME(TOF_RANGE_RESPONSE_NOTIF), }; /* Please keep this array *SORTED* by hex value. * Access is done through binary search */ static const struct iwl_hcmd_names iwl_mld_phy_names[] = { HCMD_NAME(CMD_DTS_MEASUREMENT_TRIGGER_WIDE), HCMD_NAME(CTDP_CONFIG_CMD), HCMD_NAME(TEMP_REPORTING_THRESHOLDS_CMD), HCMD_NAME(PER_CHAIN_LIMIT_OFFSET_CMD), HCMD_NAME(CT_KILL_NOTIFICATION), HCMD_NAME(DTS_MEASUREMENT_NOTIF_WIDE), }; /* Please keep this array *SORTED* by hex value. * Access is done through binary search */ static const struct iwl_hcmd_names iwl_mld_statistics_names[] = { HCMD_NAME(STATISTICS_OPER_NOTIF), HCMD_NAME(STATISTICS_OPER_PART1_NOTIF), }; /* Please keep this array *SORTED* by hex value. * Access is done through binary search */ static const struct iwl_hcmd_names iwl_mld_prot_offload_names[] = { HCMD_NAME(STORED_BEACON_NTF), }; /* Please keep this array *SORTED* by hex value. * Access is done through binary search */ static const struct iwl_hcmd_names iwl_mld_coex_names[] = { HCMD_NAME(PROFILE_NOTIF), }; VISIBLE_IF_IWLWIFI_KUNIT const struct iwl_hcmd_arr iwl_mld_groups[] = { [LEGACY_GROUP] = HCMD_ARR(iwl_mld_legacy_names), [LONG_GROUP] = HCMD_ARR(iwl_mld_legacy_names), [SYSTEM_GROUP] = HCMD_ARR(iwl_mld_system_names), [MAC_CONF_GROUP] = HCMD_ARR(iwl_mld_mac_conf_names), [DATA_PATH_GROUP] = HCMD_ARR(iwl_mld_data_path_names), [LOCATION_GROUP] = HCMD_ARR(iwl_mld_location_names), [REGULATORY_AND_NVM_GROUP] = HCMD_ARR(iwl_mld_reg_and_nvm_names), [DEBUG_GROUP] = HCMD_ARR(iwl_mld_debug_names), [PHY_OPS_GROUP] = HCMD_ARR(iwl_mld_phy_names), [STATISTICS_GROUP] = HCMD_ARR(iwl_mld_statistics_names), [PROT_OFFLOAD_GROUP] = HCMD_ARR(iwl_mld_prot_offload_names), [BT_COEX_GROUP] = HCMD_ARR(iwl_mld_coex_names), }; EXPORT_SYMBOL_IF_IWLWIFI_KUNIT(iwl_mld_groups); #if IS_ENABLED(CONFIG_IWLWIFI_KUNIT_TESTS) const unsigned int global_iwl_mld_goups_size = ARRAY_SIZE(iwl_mld_groups); EXPORT_SYMBOL_IF_IWLWIFI_KUNIT(global_iwl_mld_goups_size); #endif static void iwl_mld_configure_trans(struct iwl_op_mode *op_mode) { const struct iwl_mld *mld = IWL_OP_MODE_GET_MLD(op_mode); static const u8 no_reclaim_cmds[] = {TX_CMD}; struct iwl_trans_config trans_cfg = { .op_mode = op_mode, /* Rx is not supported yet, but add it to avoid warnings */ .rx_buf_size = iwl_amsdu_size_to_rxb_size(), .command_groups = iwl_mld_groups, .command_groups_size = ARRAY_SIZE(iwl_mld_groups), .fw_reset_handshake = true, .queue_alloc_cmd_ver = iwl_fw_lookup_cmd_ver(mld->fw, WIDE_ID(DATA_PATH_GROUP, SCD_QUEUE_CONFIG_CMD), 0), .no_reclaim_cmds = no_reclaim_cmds, .n_no_reclaim_cmds = ARRAY_SIZE(no_reclaim_cmds), .cb_data_offs = offsetof(struct ieee80211_tx_info, driver_data[2]), }; struct iwl_trans *trans = mld->trans; trans->rx_mpdu_cmd = REPLY_RX_MPDU_CMD; trans->iml = mld->fw->iml; trans->iml_len = mld->fw->iml_len; trans->wide_cmd_header = true; iwl_trans_configure(trans, &trans_cfg); } /* ***************************************************** * op mode ops functions ***************************************************** */ #define NUM_FW_LOAD_RETRIES 3 static struct iwl_op_mode * iwl_op_mode_mld_start(struct iwl_trans *trans, const struct iwl_cfg *cfg, const struct iwl_fw *fw, struct dentry *dbgfs_dir) { struct ieee80211_hw *hw; struct iwl_op_mode *op_mode; struct iwl_mld *mld; u32 eckv_value; int ret; /* Allocate and initialize a new hardware device */ hw = ieee80211_alloc_hw(sizeof(struct iwl_op_mode) + sizeof(struct iwl_mld), &iwl_mld_hw_ops); if (!hw) return ERR_PTR(-ENOMEM); op_mode = hw->priv; op_mode->ops = &iwl_mld_ops; mld = IWL_OP_MODE_GET_MLD(op_mode); iwl_construct_mld(mld, trans, cfg, fw, hw, dbgfs_dir); iwl_mld_construct_fw_runtime(mld, trans, fw, dbgfs_dir); iwl_mld_get_bios_tables(mld); iwl_uefi_get_sgom_table(trans, &mld->fwrt); iwl_uefi_get_step_table(trans); if (iwl_bios_get_eckv(&mld->fwrt, &eckv_value)) IWL_DEBUG_RADIO(mld, "ECKV table doesn't exist in BIOS\n"); else trans->ext_32khz_clock_valid = !!eckv_value; iwl_bios_setup_step(trans, &mld->fwrt); mld->bios_enable_puncturing = iwl_uefi_get_puncturing(&mld->fwrt); iwl_mld_hw_set_regulatory(mld); /* Configure transport layer with the opmode specific params */ iwl_mld_configure_trans(op_mode); /* needed for regulatory init */ rtnl_lock(); /* Needed for sending commands */ wiphy_lock(mld->wiphy); for (int i = 0; i < NUM_FW_LOAD_RETRIES; i++) { ret = iwl_mld_load_fw(mld); if (!ret) break; } if (ret) { wiphy_unlock(mld->wiphy); rtnl_unlock(); iwl_fw_flush_dumps(&mld->fwrt); goto free_hw; } iwl_mld_stop_fw(mld); wiphy_unlock(mld->wiphy); rtnl_unlock(); ret = iwl_mld_leds_init(mld); if (ret) goto free_nvm; ret = iwl_mld_alloc_scan_cmd(mld); if (ret) goto leds_exit; ret = iwl_mld_low_latency_init(mld); if (ret) goto free_scan_cmd; ret = iwl_mld_register_hw(mld); if (ret) goto low_latency_free; iwl_mld_toggle_tx_ant(mld, &mld->mgmt_tx_ant); iwl_mld_add_debugfs_files(mld, dbgfs_dir); iwl_mld_thermal_initialize(mld); iwl_mld_ptp_init(mld); return op_mode; low_latency_free: iwl_mld_low_latency_free(mld); free_scan_cmd: kfree(mld->scan.cmd); leds_exit: iwl_mld_leds_exit(mld); free_nvm: kfree(mld->nvm_data); free_hw: ieee80211_free_hw(mld->hw); return ERR_PTR(ret); } static void iwl_op_mode_mld_stop(struct iwl_op_mode *op_mode) { struct iwl_mld *mld = IWL_OP_MODE_GET_MLD(op_mode); iwl_mld_ptp_remove(mld); iwl_mld_leds_exit(mld); wiphy_lock(mld->wiphy); iwl_mld_thermal_exit(mld); iwl_mld_low_latency_stop(mld); iwl_mld_deinit_time_sync(mld); wiphy_unlock(mld->wiphy); ieee80211_unregister_hw(mld->hw); iwl_fw_runtime_free(&mld->fwrt); iwl_mld_low_latency_free(mld); iwl_trans_op_mode_leave(mld->trans); kfree(mld->nvm_data); kfree(mld->scan.cmd); kfree(mld->error_recovery_buf); kfree(mld->mcast_filter_cmd); ieee80211_free_hw(mld->hw); } static void iwl_mld_queue_state_change(struct iwl_op_mode *op_mode, int hw_queue, bool queue_full) { struct iwl_mld *mld = IWL_OP_MODE_GET_MLD(op_mode); struct ieee80211_txq *txq; struct iwl_mld_sta *mld_sta; struct iwl_mld_txq *mld_txq; rcu_read_lock(); txq = rcu_dereference(mld->fw_id_to_txq[hw_queue]); if (!txq) { rcu_read_unlock(); if (queue_full) { /* An internal queue is not expected to become full */ IWL_WARN(mld, "Internal hw_queue %d is full! stopping all queues\n", hw_queue); /* Stop all queues, as an internal queue is not * mapped to a mac80211 one */ ieee80211_stop_queues(mld->hw); } else { ieee80211_wake_queues(mld->hw); } return; } mld_txq = iwl_mld_txq_from_mac80211(txq); mld_sta = txq->sta ? iwl_mld_sta_from_mac80211(txq->sta) : NULL; mld_txq->status.stop_full = queue_full; if (!queue_full && mld_sta && mld_sta->sta_state != IEEE80211_STA_NOTEXIST) { local_bh_disable(); iwl_mld_tx_from_txq(mld, txq); local_bh_enable(); } rcu_read_unlock(); } static void iwl_mld_queue_full(struct iwl_op_mode *op_mode, int hw_queue) { iwl_mld_queue_state_change(op_mode, hw_queue, true); } static void iwl_mld_queue_not_full(struct iwl_op_mode *op_mode, int hw_queue) { iwl_mld_queue_state_change(op_mode, hw_queue, false); } static bool iwl_mld_set_hw_rfkill_state(struct iwl_op_mode *op_mode, bool state) { struct iwl_mld *mld = IWL_OP_MODE_GET_MLD(op_mode); iwl_mld_set_hwkill(mld, state); return false; } static void iwl_mld_free_skb(struct iwl_op_mode *op_mode, struct sk_buff *skb) { struct iwl_mld *mld = IWL_OP_MODE_GET_MLD(op_mode); struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); iwl_trans_free_tx_cmd(mld->trans, info->driver_data[1]); ieee80211_free_txskb(mld->hw, skb); } static void iwl_mld_read_error_recovery_buffer(struct iwl_mld *mld) { u32 src_size = mld->fw->ucode_capa.error_log_size; u32 src_addr = mld->fw->ucode_capa.error_log_addr; u8 *recovery_buf; int ret; /* no recovery buffer size defined in a TLV */ if (!src_size) return; recovery_buf = kzalloc(src_size, GFP_ATOMIC); if (!recovery_buf) return; ret = iwl_trans_read_mem_bytes(mld->trans, src_addr, recovery_buf, src_size); if (ret) { IWL_ERR(mld, "Failed to read error recovery buffer (%d)\n", ret); kfree(recovery_buf); return; } mld->error_recovery_buf = recovery_buf; } static void iwl_mld_restart_nic(struct iwl_mld *mld) { iwl_mld_read_error_recovery_buffer(mld); mld->fwrt.trans->dbg.restart_required = false; ieee80211_restart_hw(mld->hw); } static void iwl_mld_nic_error(struct iwl_op_mode *op_mode, enum iwl_fw_error_type type) { struct iwl_mld *mld = IWL_OP_MODE_GET_MLD(op_mode); bool trans_dead = test_bit(STATUS_TRANS_DEAD, &mld->trans->status); if (type == IWL_ERR_TYPE_CMD_QUEUE_FULL) IWL_ERR(mld, "Command queue full!\n"); else if (!trans_dead && !mld->fw_status.do_not_dump_once) iwl_fwrt_dump_error_logs(&mld->fwrt); mld->fw_status.do_not_dump_once = false; /* It is necessary to abort any os scan here because mac80211 requires * having the scan cleared before restarting. * We'll reset the scan_status to NONE in restart cleanup in * the next drv_start() call from mac80211. If ieee80211_hw_restart * isn't called scan status will stay busy. */ iwl_mld_report_scan_aborted(mld); /* * This should be first thing before trying to collect any * data to avoid endless loops if any HW error happens while * collecting debug data. * It might not actually be true that we'll restart, but the * setting doesn't matter if we're going to be unbound either. */ if (type != IWL_ERR_TYPE_RESET_HS_TIMEOUT) mld->fw_status.in_hw_restart = true; } static void iwl_mld_dump_error(struct iwl_op_mode *op_mode, struct iwl_fw_error_dump_mode *mode) { struct iwl_mld *mld = IWL_OP_MODE_GET_MLD(op_mode); /* if we come in from opmode we have the mutex held */ if (mode->context == IWL_ERR_CONTEXT_FROM_OPMODE) { lockdep_assert_wiphy(mld->wiphy); iwl_fw_error_collect(&mld->fwrt); } else { wiphy_lock(mld->wiphy); if (mode->context != IWL_ERR_CONTEXT_ABORT) iwl_fw_error_collect(&mld->fwrt); wiphy_unlock(mld->wiphy); } } static bool iwl_mld_sw_reset(struct iwl_op_mode *op_mode, enum iwl_fw_error_type type) { struct iwl_mld *mld = IWL_OP_MODE_GET_MLD(op_mode); /* Do restart only in the following conditions are met: * - we consider the FW as running * - The trigger that brought us here is defined as one that requires * a restart (in the debug TLVs) */ if (!mld->fw_status.running || !mld->fwrt.trans->dbg.restart_required) return false; iwl_mld_restart_nic(mld); return true; } static void iwl_mld_time_point(struct iwl_op_mode *op_mode, enum iwl_fw_ini_time_point tp_id, union iwl_dbg_tlv_tp_data *tp_data) { struct iwl_mld *mld = IWL_OP_MODE_GET_MLD(op_mode); iwl_dbg_tlv_time_point(&mld->fwrt, tp_id, tp_data); } #ifdef CONFIG_PM_SLEEP static void iwl_mld_device_powered_off(struct iwl_op_mode *op_mode) { struct iwl_mld *mld = IWL_OP_MODE_GET_MLD(op_mode); wiphy_lock(mld->wiphy); mld->trans->system_pm_mode = IWL_PLAT_PM_MODE_DISABLED; iwl_mld_stop_fw(mld); mld->fw_status.in_d3 = false; wiphy_unlock(mld->wiphy); } #else static void iwl_mld_device_powered_off(struct iwl_op_mode *op_mode) {} #endif static const struct iwl_op_mode_ops iwl_mld_ops = { .start = iwl_op_mode_mld_start, .stop = iwl_op_mode_mld_stop, .rx = iwl_mld_rx, .rx_rss = iwl_mld_rx_rss, .queue_full = iwl_mld_queue_full, .queue_not_full = iwl_mld_queue_not_full, .hw_rf_kill = iwl_mld_set_hw_rfkill_state, .free_skb = iwl_mld_free_skb, .nic_error = iwl_mld_nic_error, .dump_error = iwl_mld_dump_error, .sw_reset = iwl_mld_sw_reset, .time_point = iwl_mld_time_point, .device_powered_off = pm_sleep_ptr(iwl_mld_device_powered_off), }; struct iwl_mld_mod_params iwlmld_mod_params = { .power_scheme = IWL_POWER_SCHEME_BPS, }; module_param_named(power_scheme, iwlmld_mod_params.power_scheme, int, 0444); MODULE_PARM_DESC(power_scheme, "power management scheme: 1-active, 2-balanced, default: 2");